fix update access token may notwork.
This commit is contained in:
parent
0ebc21a744
commit
f42e79be3e
|
@ -19,8 +19,10 @@
|
|||
<w>buggie</w>
|
||||
<w>bumptech</w>
|
||||
<w>codepoint</w>
|
||||
<w>coord</w>
|
||||
<w>coroutine</w>
|
||||
<w>coroutines</w>
|
||||
<w>denomi</w>
|
||||
<w>doctype</w>
|
||||
<w>dont</w>
|
||||
<w>emoji</w>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</component>
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myDefaultNotNull" value="android.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="12">
|
||||
|
|
|
@ -496,8 +496,8 @@ class ActMain : AppCompatActivity()
|
|||
bStart = true
|
||||
log.d("onStart")
|
||||
|
||||
var ts= SystemClock.elapsedRealtime()
|
||||
var te:Long
|
||||
var ts = SystemClock.elapsedRealtime()
|
||||
var te : Long
|
||||
|
||||
// カラーカスタマイズを読み直す
|
||||
ListDivider.color = Pref.ipListDividerColor(pref)
|
||||
|
@ -512,9 +512,9 @@ class ActMain : AppCompatActivity()
|
|||
CustomShare.reloadCache(this, pref)
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms : reload color")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms : reload color")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
|
||||
var tz = TimeZone.getDefault()
|
||||
try {
|
||||
val tz_id = Pref.spTimeZone(pref)
|
||||
|
@ -527,9 +527,9 @@ class ActMain : AppCompatActivity()
|
|||
TootStatus.date_format.timeZone = tz
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms : reload timezone")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms : reload timezone")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
|
||||
// バグいアカウントデータを消す
|
||||
try {
|
||||
SavedAccount.sweepBuggieData()
|
||||
|
@ -538,7 +538,7 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms : sweepBuggieData")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms : sweepBuggieData")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
||||
|
@ -560,7 +560,7 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms : column order")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms : column order")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
// 背景画像を表示しない設定が変更された時にカラムの背景を設定しなおす
|
||||
|
@ -569,22 +569,21 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms :fireColumnColor")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms :fireColumnColor")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
// 各カラムのアカウント設定を読み直す
|
||||
reloadAccountSetting()
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms :reloadAccountSetting")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms :reloadAccountSetting")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
|
||||
// 投稿直後ならカラムの再取得を行う
|
||||
refreshAfterPost()
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms :refreshAfterPost")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms :refreshAfterPost")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
// 画面復帰時に再取得やストリーミング開始を行う
|
||||
|
@ -593,14 +592,14 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms :column.onStart")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms :column.onStart")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
// カラムの表示範囲インジケータを更新
|
||||
updateColumnStripSelection(- 1, - 1f)
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms :updateColumnStripSelection")
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms :updateColumnStripSelection")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
|
||||
|
@ -609,16 +608,14 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-ts >= 100L) log.w("onStart: ${te-ts}ms :fireShowContent")
|
||||
ts = SystemClock.elapsedRealtime()
|
||||
|
||||
if(te - ts >= 100L) log.w("onStart: ${te - ts}ms :fireShowContent")
|
||||
|
||||
// 相対時刻表示
|
||||
proc_updateRelativeTime.run()
|
||||
|
||||
|
||||
te = SystemClock.elapsedRealtime()
|
||||
if( te-tsTotal >= 100L) log.w("onStart: ${te-tsTotal}ms : total")
|
||||
if(te - tsTotal >= 100L) log.w("onStart: ${te - tsTotal}ms : total")
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
@ -1768,8 +1765,8 @@ class ActMain : AppCompatActivity()
|
|||
client.instance = instance
|
||||
}
|
||||
|
||||
val (r2, ti) = TootInstance.get(client)
|
||||
if(ti == null) return r2
|
||||
val (ti, r2) = TootInstance.get(client)
|
||||
ti ?: return r2
|
||||
|
||||
this.host = instance
|
||||
val client_name = Pref.spClientName(this@ActMain)
|
||||
|
@ -1881,7 +1878,11 @@ class ActMain : AppCompatActivity()
|
|||
// cancelled.
|
||||
}
|
||||
|
||||
error != null -> showToast(this@ActMain, true, "${result.error} ${result.requestInfo}".trim() )
|
||||
error != null -> showToast(
|
||||
this@ActMain,
|
||||
true,
|
||||
"${result.error} ${result.requestInfo}".trim()
|
||||
)
|
||||
|
||||
token_info == null -> showToast(this@ActMain, true, "can't get access token.")
|
||||
|
||||
|
@ -1992,8 +1993,8 @@ class ActMain : AppCompatActivity()
|
|||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
|
||||
val (instanceResult, instance) = TootInstance.get(client,host)
|
||||
if(instance == null) return instanceResult
|
||||
val (instance, instanceResult) = TootInstance.get(client, host)
|
||||
instance ?: return instanceResult
|
||||
|
||||
val misskeyVersion = instance.misskeyVersion
|
||||
val linkHelper = LinkHelper.newLinkHelper(host, misskeyVersion = misskeyVersion)
|
||||
|
|
|
@ -97,24 +97,22 @@ class ActPost : AppCompatActivity(),
|
|||
internal const val MIME_TYPE_JPEG = "image/jpeg"
|
||||
internal const val MIME_TYPE_PNG = "image/png"
|
||||
|
||||
|
||||
|
||||
internal val resizeConfigList = arrayOf(
|
||||
ResizeConfig(ResizeType.None,0),
|
||||
|
||||
ResizeConfig(ResizeType.LongSide,640),
|
||||
ResizeConfig(ResizeType.LongSide,800),
|
||||
ResizeConfig(ResizeType.LongSide,1024),
|
||||
ResizeConfig(ResizeType.LongSide,1280),
|
||||
ResizeConfig(ResizeType.LongSide,1600),
|
||||
ResizeConfig(ResizeType.LongSide,2048),
|
||||
ResizeConfig(ResizeType.None, 0),
|
||||
|
||||
ResizeConfig(ResizeType.SquarePixel,640),
|
||||
ResizeConfig(ResizeType.SquarePixel,800),
|
||||
ResizeConfig(ResizeType.SquarePixel,1024),
|
||||
ResizeConfig(ResizeType.SquarePixel,1280),
|
||||
ResizeConfig(ResizeType.SquarePixel,1600),
|
||||
ResizeConfig(ResizeType.SquarePixel,2048)
|
||||
ResizeConfig(ResizeType.LongSide, 640),
|
||||
ResizeConfig(ResizeType.LongSide, 800),
|
||||
ResizeConfig(ResizeType.LongSide, 1024),
|
||||
ResizeConfig(ResizeType.LongSide, 1280),
|
||||
ResizeConfig(ResizeType.LongSide, 1600),
|
||||
ResizeConfig(ResizeType.LongSide, 2048),
|
||||
|
||||
ResizeConfig(ResizeType.SquarePixel, 640),
|
||||
ResizeConfig(ResizeType.SquarePixel, 800),
|
||||
ResizeConfig(ResizeType.SquarePixel, 1024),
|
||||
ResizeConfig(ResizeType.SquarePixel, 1280),
|
||||
ResizeConfig(ResizeType.SquarePixel, 1600),
|
||||
ResizeConfig(ResizeType.SquarePixel, 2048)
|
||||
)
|
||||
|
||||
internal val acceptable_mime_types = HashSet<String>().apply {
|
||||
|
@ -1309,7 +1307,7 @@ class ActPost : AppCompatActivity(),
|
|||
var newInfo : TootInstance? = null
|
||||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
val (result, ti) = TootInstance.get(client)
|
||||
val (ti, result) = TootInstance.get(client)
|
||||
newInfo = ti
|
||||
return result
|
||||
}
|
||||
|
@ -2121,8 +2119,8 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
client.account = account
|
||||
|
||||
val (tiResult, ti) = TootInstance.get(client)
|
||||
if(ti == null) return tiResult
|
||||
val (ti, tiResult) = TootInstance.get(client)
|
||||
ti ?: return tiResult
|
||||
|
||||
if(ti.instanceType == TootInstance.InstanceType.Pixelfed) {
|
||||
if(in_reply_to_id != null) {
|
||||
|
|
|
@ -4,21 +4,28 @@ import android.content.BroadcastReceiver
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import jp.juggler.util.LogCategory
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class ChooseReceiver :BroadcastReceiver(){
|
||||
class ChooseReceiver : BroadcastReceiver() {
|
||||
|
||||
companion object{
|
||||
var lastComponentName: ComponentName? = null
|
||||
var refCallback : WeakReference<()->Unit>? = null
|
||||
companion object {
|
||||
private val log = LogCategory("ChooseReceiver")
|
||||
var lastComponentName : ComponentName? = null
|
||||
var refCallback : WeakReference<() -> Unit>? = null
|
||||
|
||||
fun setCallback(cb:()->Unit){
|
||||
fun setCallback(cb : () -> Unit) {
|
||||
refCallback = WeakReference(cb)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context,intent: Intent?) {
|
||||
lastComponentName = intent?.extras?.get(Intent.EXTRA_CHOSEN_COMPONENT) as? ComponentName
|
||||
refCallback?.get()?.invoke()
|
||||
override fun onReceive(context : Context, intent : Intent?) {
|
||||
if(Build.VERSION.SDK_INT >= 22) {
|
||||
lastComponentName = intent?.extras?.get(Intent.EXTRA_CHOSEN_COMPONENT) as? ComponentName
|
||||
refCallback?.get()?.invoke()
|
||||
} else {
|
||||
log.w("onReceive: Intent.EXTRA_CHOSEN_COMPONENT can't be used in API level 21")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,9 +48,9 @@ class ColumnTask_Loading(
|
|||
|
||||
column.muted_word2 = column.encodeFilterTree(column.loadFilter2(client))
|
||||
|
||||
if( !access_info.isNA) {
|
||||
val (instanceResult, instance) = TootInstance.get(client)
|
||||
if(instance == null) return instanceResult
|
||||
if(! access_info.isNA) {
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
instance ?: return instanceResult
|
||||
if(instance.instanceType == TootInstance.InstanceType.Pixelfed) {
|
||||
return TootApiResult("currently Pixelfed instance is not supported.")
|
||||
}
|
||||
|
@ -121,7 +121,6 @@ class ColumnTask_Loading(
|
|||
/////////////////////////////////////////////////////////////////
|
||||
// functions that called from ColumnTask.loading lambda.
|
||||
|
||||
|
||||
internal fun getStatusesPinned(client : TootApiClient, path_base : String) {
|
||||
val result = client.request(path_base)
|
||||
val jsonArray = result?.jsonArray
|
||||
|
@ -691,8 +690,8 @@ class ColumnTask_Loading(
|
|||
) : TootApiResult? {
|
||||
// (Mastodonのみ対応)
|
||||
|
||||
val (instanceResult, instance) = TootInstance.get(client)
|
||||
if(instance == null) return instanceResult
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
instance ?: return instanceResult
|
||||
|
||||
// ステータスIDに該当するトゥート
|
||||
// タンスをまたいだりすると存在しないかもしれないが、エラーは出さない
|
||||
|
@ -735,8 +734,8 @@ class ColumnTask_Loading(
|
|||
internal fun getAccountAroundStatuses(client : TootApiClient) : TootApiResult? {
|
||||
// (Mastodonのみ対応)
|
||||
|
||||
val (instanceResult, instance) = TootInstance.get(client)
|
||||
if(instance == null) return instanceResult
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
instance ?: return instanceResult
|
||||
|
||||
// ステータスIDに該当するトゥート
|
||||
// タンスをまたいだりすると存在しないかもしれない
|
||||
|
@ -986,8 +985,8 @@ class ColumnTask_Loading(
|
|||
return TootApiResult(context.getString(R.string.search_is_not_available_on_pseudo_account))
|
||||
}
|
||||
|
||||
val(instanceResult,instance) = TootInstance.get(client)
|
||||
if( instance==null) return instanceResult
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
instance ?: return instanceResult
|
||||
|
||||
var query = "q=${column.search_query.encodePercent()}"
|
||||
if(column.search_resolve) query += "&resolve=1"
|
||||
|
|
|
@ -57,10 +57,10 @@ enum class ColumnType(
|
|||
|
||||
ProfileStatusMastodon(
|
||||
loading = { client ->
|
||||
val(instanceResult,instance) = TootInstance.get(client)
|
||||
if(instance==null){
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
if(instance == null) {
|
||||
instanceResult
|
||||
}else {
|
||||
} else {
|
||||
val path = column.makeProfileStatusesUrl(column.profile_id)
|
||||
|
||||
if(instance.versionGE(TootInstance.VERSION_1_6)
|
||||
|
@ -1095,26 +1095,31 @@ enum class ColumnType(
|
|||
headerType = HeaderType.Instance,
|
||||
|
||||
loading = { client ->
|
||||
val(instanceResult,instance) = TootInstance.get(client,column.instance_uri,allowPixelfed = true)
|
||||
if(instance!=null) {
|
||||
val (instance, instanceResult) = TootInstance.get(
|
||||
client,
|
||||
column.instance_uri,
|
||||
allowPixelfed = true,
|
||||
forceUpdate = true
|
||||
)
|
||||
if(instance != null) {
|
||||
column.instance_information = instance
|
||||
column.handshake = instanceResult?.response?.handshake
|
||||
}
|
||||
instanceResult
|
||||
//
|
||||
// // 「インスタンス情報」カラムをNAアカウントで開く場合
|
||||
// instance_name != null -> client.instance = instance_name
|
||||
//
|
||||
// val (result, ti) = client.parseInstanceInformation(client.getInstanceInformation())
|
||||
// instance_tmp = ti
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// val result = getInstanceInformation(client, column.instance_uri)
|
||||
// if(instance_tmp != null) {
|
||||
//
|
||||
// }
|
||||
// result
|
||||
//
|
||||
// // 「インスタンス情報」カラムをNAアカウントで開く場合
|
||||
// instance_name != null -> client.instance = instance_name
|
||||
//
|
||||
// val (result, ti) = client.parseInstanceInformation(client.getInstanceInformation())
|
||||
// instance_tmp = ti
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// val result = getInstanceInformation(client, column.instance_uri)
|
||||
// if(instance_tmp != null) {
|
||||
//
|
||||
// }
|
||||
// result
|
||||
}
|
||||
),
|
||||
|
||||
|
@ -1342,7 +1347,7 @@ enum class ColumnType(
|
|||
PROFILE_DIRECTORY(36,
|
||||
iconId = { R.drawable.ic_follow_plus },
|
||||
name1 = { it.getString(R.string.profile_directory) },
|
||||
name2 = { context.getString(R.string.profile_directory_of, instance_uri)},
|
||||
name2 = { context.getString(R.string.profile_directory_of, instance_uri) },
|
||||
bAllowPseudo = true,
|
||||
headerType = HeaderType.ProfileDirectory,
|
||||
loading = { client ->
|
||||
|
@ -1358,8 +1363,8 @@ enum class ColumnType(
|
|||
iconId = { R.drawable.ic_account_box },
|
||||
name1 = { it.getString(R.string.account_tl_around) },
|
||||
name2 = {
|
||||
val id =status_id?.toString() ?: "null"
|
||||
context.getString(R.string.account_tl_around_of,id)
|
||||
val id = status_id?.toString() ?: "null"
|
||||
context.getString(R.string.account_tl_around_of, id)
|
||||
},
|
||||
|
||||
loading = { client -> getAccountAroundStatuses(client) },
|
||||
|
|
|
@ -1089,7 +1089,7 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
if(wps.flags != 0) {
|
||||
job.bPollingRequired.set(true)
|
||||
|
||||
val (instanceResult, instance) = TootInstance.get(client)
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
if(instance == null) {
|
||||
if(instanceResult != null) log.e("${instanceResult.error} ${instanceResult.requestInfo}".trim())
|
||||
return
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package jp.juggler.subwaytooter.action
|
||||
|
||||
import android.content.Context
|
||||
import jp.juggler.subwaytooter.App1
|
||||
import jp.juggler.subwaytooter.Pref
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.TootRelationShip
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.util.LogCategory
|
||||
|
@ -32,19 +32,21 @@ internal fun addPseudoAccount(
|
|||
}
|
||||
|
||||
if(instanceInfo == null) {
|
||||
TootTaskRunner(context).run(host,object : TootTask {
|
||||
TootTaskRunner(context).run(host, object : TootTask {
|
||||
|
||||
var targetInstance :TootInstance? =null
|
||||
var targetInstance : TootInstance? = null
|
||||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
val(instanceResult,instance) = TootInstance.get(client)
|
||||
if(instance!=null) targetInstance = instance
|
||||
val (instance, instanceResult) = TootInstance.get(client)
|
||||
targetInstance = instance
|
||||
return instanceResult
|
||||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) =when{
|
||||
result==null ->{}
|
||||
targetInstance == null -> showToast(context,false,result.error)
|
||||
override fun handleResult(result : TootApiResult?) = when {
|
||||
result == null -> {
|
||||
}
|
||||
|
||||
targetInstance == null -> showToast(context, false, result.error)
|
||||
else -> addPseudoAccount(context, host, targetInstance, callback)
|
||||
}
|
||||
})
|
||||
|
@ -62,7 +64,7 @@ internal fun addPseudoAccount(
|
|||
JSONObject(),
|
||||
misskeyVersion = instanceInfo.misskeyVersion
|
||||
)
|
||||
|
||||
|
||||
account = SavedAccount.loadAccount(context, row_id)
|
||||
if(account == null) {
|
||||
throw RuntimeException("loadAccount returns null.")
|
||||
|
@ -122,24 +124,24 @@ internal fun saveUserRelationMisskey(
|
|||
return relation
|
||||
}
|
||||
|
||||
// relationshipを取得
|
||||
internal fun loadRelation1Mastodon(
|
||||
client : TootApiClient,
|
||||
access_info : SavedAccount,
|
||||
who : TootAccount
|
||||
) : RelationResult {
|
||||
val rr = RelationResult()
|
||||
rr.result = client.request("/api/v1/accounts/relationships?id=${who.id}")
|
||||
val r2 = rr.result
|
||||
val jsonArray = r2?.jsonArray
|
||||
if(jsonArray != null) {
|
||||
val list = parseList(::TootRelationShip, TootParser(client.context, access_info), jsonArray)
|
||||
if(list.isNotEmpty()) {
|
||||
rr.relation = saveUserRelation(access_info, list[0])
|
||||
}
|
||||
}
|
||||
return rr
|
||||
}
|
||||
//// relationshipを取得
|
||||
//internal fun loadRelation1Mastodon(
|
||||
// client : TootApiClient,
|
||||
// access_info : SavedAccount,
|
||||
// who : TootAccount
|
||||
//) : RelationResult {
|
||||
// val rr = RelationResult()
|
||||
// rr.result = client.request("/api/v1/accounts/relationships?id=${who.id}")
|
||||
// val r2 = rr.result
|
||||
// val jsonArray = r2?.jsonArray
|
||||
// if(jsonArray != null) {
|
||||
// val list = parseList(::TootRelationShip, TootParser(client.context, access_info), jsonArray)
|
||||
// if(list.isNotEmpty()) {
|
||||
// rr.relation = saveUserRelation(access_info, list[0])
|
||||
// }
|
||||
// }
|
||||
// return rr
|
||||
//}
|
||||
|
||||
// 別アカ操作と別タンスの関係
|
||||
const val NOT_CROSS_ACCOUNT = 1
|
||||
|
|
|
@ -36,7 +36,7 @@ object Action_Account {
|
|||
LoginForm.Action.Create -> client.createUser1(Pref.spClientName(activity))
|
||||
|
||||
LoginForm.Action.Pseudo, LoginForm.Action.Token -> {
|
||||
val (ri, ti) = TootInstance.get(client)
|
||||
val (ti, ri) = TootInstance.get(client)
|
||||
if(ti != null) ri?.data = ti
|
||||
ri
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ object Action_Instance {
|
|||
instance == null -> TootTaskRunner(activity).run(host, object : TootTask {
|
||||
var targetInstance : TootInstance? = null
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
val (ri, ti) = TootInstance.get(client, host, allowPixelfed = true)
|
||||
val (ti, ri) = TootInstance.get(client, host, allowPixelfed = true)
|
||||
targetInstance = ti
|
||||
return ri
|
||||
}
|
||||
|
|
|
@ -1039,13 +1039,11 @@ class TootApiClient(
|
|||
forceUpdateClient : Boolean = false
|
||||
) : TootApiResult? {
|
||||
|
||||
val (ri, ti) = TootInstance.get(this)
|
||||
return if(ti == null || (ri?.response?.code ?: 0) !in 200 until 300) {
|
||||
ri
|
||||
} else if(ti.misskeyVersion > 0) {
|
||||
authentication1Misskey(clientNameArg, ti)
|
||||
} else {
|
||||
authentication1Mastodon(clientNameArg, ti, forceUpdateClient)
|
||||
val (ti, ri) = TootInstance.get(this)
|
||||
ti ?: return ri
|
||||
return when {
|
||||
ti.misskeyVersion > 0 -> authentication1Misskey(clientNameArg, ti)
|
||||
else -> authentication1Mastodon(clientNameArg, ti, forceUpdateClient)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1150,8 +1148,9 @@ class TootApiClient(
|
|||
|
||||
fun createUser1(clientNameArg : String) : TootApiResult? {
|
||||
|
||||
val (ri, ti) = TootInstance.get(this)
|
||||
if(ti == null) return ri
|
||||
val (ti, ri) = TootInstance.get(this)
|
||||
ti ?: return ri
|
||||
|
||||
return when(ti.instanceType) {
|
||||
TootInstance.InstanceType.Misskey ->
|
||||
TootApiResult("Misskey has no API to create new account")
|
||||
|
@ -1185,15 +1184,15 @@ class TootApiClient(
|
|||
|
||||
|
||||
if(! sendRequest(result) {
|
||||
|
||||
val params = ArrayList<String>().apply{
|
||||
|
||||
val params = ArrayList<String>().apply {
|
||||
add("username=${username.encodePercent()}")
|
||||
add("email=${email.encodePercent()}")
|
||||
add("password=${password.encodePercent()}")
|
||||
add("agreement=${agreement}")
|
||||
if( reason?.isNotEmpty() == true ) add("reason=${reason.encodePercent()}")
|
||||
if(reason?.isNotEmpty() == true) add("reason=${reason.encodePercent()}")
|
||||
}
|
||||
|
||||
|
||||
params
|
||||
.joinToString("&").toFormRequestBody().toPost()
|
||||
.url("https://$instance/api/v1/accounts")
|
||||
|
|
|
@ -269,8 +269,9 @@ class TootInstance(parser : TootParser, src : JSONObject) {
|
|||
client : TootApiClient,
|
||||
host : String? = client.instance,
|
||||
account : SavedAccount? = if(host == client.instance) client.account else null,
|
||||
allowPixelfed : Boolean = false
|
||||
) : Pair<TootApiResult?, TootInstance?> {
|
||||
allowPixelfed : Boolean = false,
|
||||
forceUpdate :Boolean = false
|
||||
) : Pair<TootInstance?,TootApiResult?> {
|
||||
|
||||
val tmpInstance = client.instance
|
||||
val tmpAccount = client.account
|
||||
|
@ -282,23 +283,27 @@ class TootInstance(parser : TootParser, src : JSONObject) {
|
|||
// ホスト名ごとに用意したオブジェクトで同期する
|
||||
val cacheEntry = getCacheEntry(instanceName)
|
||||
synchronized(cacheEntry) {
|
||||
// re-use cached item.
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
var item = cacheEntry.data
|
||||
if(item != null && now - item.time_parse <= EXPIRE) {
|
||||
|
||||
if(item.instanceType == InstanceType.Pixelfed &&
|
||||
! Pref.bpEnablePixelfed(App1.pref) &&
|
||||
! allowPixelfed
|
||||
) {
|
||||
return Pair(
|
||||
TootApiResult("currently Pixelfed instance is not supported."),
|
||||
null
|
||||
)
|
||||
|
||||
var item: TootInstance?
|
||||
|
||||
if(!forceUpdate) {
|
||||
// re-use cached item.
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
item = cacheEntry.data
|
||||
if(item != null && now - item.time_parse <= EXPIRE) {
|
||||
|
||||
if(item.instanceType == InstanceType.Pixelfed &&
|
||||
! Pref.bpEnablePixelfed(App1.pref) &&
|
||||
! allowPixelfed
|
||||
) {
|
||||
return Pair(
|
||||
null,
|
||||
TootApiResult("currently Pixelfed instance is not supported.")
|
||||
)
|
||||
}
|
||||
|
||||
return Pair(item,TootApiResult() )
|
||||
}
|
||||
|
||||
return Pair(TootApiResult(), item)
|
||||
|
||||
}
|
||||
|
||||
// get new information
|
||||
|
@ -315,7 +320,7 @@ class TootInstance(parser : TootParser, src : JSONObject) {
|
|||
client.getInstanceInformation()
|
||||
}
|
||||
|
||||
val json = result?.jsonObject ?: return Pair(result, null)
|
||||
val json = result?.jsonObject ?: return Pair(null,result)
|
||||
|
||||
item = parseItem(
|
||||
::TootInstance,
|
||||
|
@ -335,19 +340,22 @@ class TootInstance(parser : TootParser, src : JSONObject) {
|
|||
|
||||
return when {
|
||||
item == null ->
|
||||
Pair(result.setError("can't parse data in instance information."), null)
|
||||
Pair(
|
||||
null,
|
||||
result.setError("instance information parse error.")
|
||||
)
|
||||
|
||||
item.instanceType == InstanceType.Pixelfed &&
|
||||
! Pref.bpEnablePixelfed(App1.pref) &&
|
||||
! allowPixelfed ->
|
||||
Pair(
|
||||
result.setError("currently Pixelfed instance is not supported."),
|
||||
null
|
||||
null,
|
||||
result.setError("currently Pixelfed instance is not supported.")
|
||||
)
|
||||
|
||||
else -> {
|
||||
cacheEntry.data = item
|
||||
Pair(result, item)
|
||||
Pair(item,result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,8 @@ object LoginForm {
|
|||
activity.resources.openRawResource(R.raw.server_list).use { inStream ->
|
||||
val br = BufferedReader(InputStreamReader(inStream, "UTF-8"))
|
||||
while(true) {
|
||||
val s : String = br.readLine()?.trim { it <= ' ' }?.toLowerCase(Locale.JAPAN) ?: break
|
||||
val s : String =
|
||||
br.readLine()?.trim { it <= ' ' }?.toLowerCase(Locale.JAPAN) ?: break
|
||||
if(s.isNotEmpty()) instance_list.add(s)
|
||||
}
|
||||
}
|
||||
|
@ -145,10 +146,10 @@ object LoginForm {
|
|||
return value as String
|
||||
}
|
||||
|
||||
override fun performFiltering(constraint : CharSequence?) : Filter.FilterResults {
|
||||
val result = Filter.FilterResults()
|
||||
override fun performFiltering(constraint : CharSequence?) : FilterResults {
|
||||
val result = FilterResults()
|
||||
if(constraint?.isNotEmpty() == true) {
|
||||
val key = constraint.toString().toLowerCase()
|
||||
val key = constraint.toString().toLowerCase(Locale.JAPAN)
|
||||
// suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある
|
||||
val suggestions = StringArray()
|
||||
for(s in instance_list) {
|
||||
|
@ -165,7 +166,7 @@ object LoginForm {
|
|||
|
||||
override fun publishResults(
|
||||
constraint : CharSequence?,
|
||||
results : Filter.FilterResults?
|
||||
results : FilterResults?
|
||||
) {
|
||||
clear()
|
||||
val values = results?.values
|
||||
|
|
|
@ -350,8 +350,8 @@ class PostHelper(
|
|||
|
||||
var visibility_checked : TootVisibility? = visibility
|
||||
|
||||
val (ri, instance) = TootInstance.get(client)
|
||||
if(instance == null) return ri
|
||||
val (instance, ri) = TootInstance.get(client)
|
||||
instance ?: return ri
|
||||
|
||||
if(instance.instanceType == TootInstance.InstanceType.Pixelfed) {
|
||||
if(in_reply_to_id != null && attachment_list?.isNotEmpty() == true) {
|
||||
|
|
|
@ -89,15 +89,15 @@ class PushSubscriptionHelper(
|
|||
.toPostRequestBuilder()
|
||||
.url("${PollingWorker.APP_SERVER}/webpushserverkey")
|
||||
.build()
|
||||
|
||||
|
||||
)
|
||||
val res = r?.response
|
||||
when(res?.code) {
|
||||
|
||||
null ->{
|
||||
|
||||
null -> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
200 -> {
|
||||
// 登録できたサーバーキーをアプリ内DBに保存
|
||||
SubscriptionServerKey.save(clientIdentifier, serverKey)
|
||||
|
@ -188,7 +188,7 @@ class PushSubscriptionHelper(
|
|||
var subscription404 = false
|
||||
when(res.code) {
|
||||
200 -> {
|
||||
if( r.error?.isNotEmpty() == true && r.jsonObject == null ){
|
||||
if(r.error?.isNotEmpty() == true && r.jsonObject == null) {
|
||||
// Pleromaが200応用でエラーHTMLを返す
|
||||
return TootApiResult(
|
||||
error = context.getString(
|
||||
|
@ -196,7 +196,7 @@ class PushSubscriptionHelper(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// たぶん購読が存在する
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@ class PushSubscriptionHelper(
|
|||
}
|
||||
}
|
||||
|
||||
in 400 until 500 ->{
|
||||
in 400 until 500 -> {
|
||||
return TootApiResult(
|
||||
error = context.getString(
|
||||
R.string.instance_does_not_support_push_api_pleroma
|
||||
|
@ -234,9 +234,9 @@ class PushSubscriptionHelper(
|
|||
if(oldSubscription == null) {
|
||||
|
||||
// 現在の購読状況が分からない場合はインスタンスのバージョンを調べる必要がある
|
||||
val(result,ti) = TootInstance.get(client)
|
||||
val (ti, result) = TootInstance.get(client)
|
||||
ti ?: return result
|
||||
|
||||
|
||||
if(! ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) {
|
||||
// 2.4.0rc1 未満にはプッシュ購読APIはない
|
||||
return TootApiResult(
|
||||
|
@ -255,6 +255,7 @@ class PushSubscriptionHelper(
|
|||
if(verbose) addLog(context.getString(R.string.push_subscription_not_exists))
|
||||
return TootApiResult()
|
||||
}
|
||||
|
||||
else -> {
|
||||
// 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない
|
||||
}
|
||||
|
@ -296,7 +297,7 @@ class PushSubscriptionHelper(
|
|||
r = client.http(
|
||||
JSONObject()
|
||||
.put("token_digest", tokenDigest)
|
||||
.put("install_id", install_id)
|
||||
.put("install_id", install_id)
|
||||
.toPostRequestBuilder()
|
||||
.url("${PollingWorker.APP_SERVER}/webpushtokencheck")
|
||||
.build()
|
||||
|
|
|
@ -7,9 +7,9 @@ import android.graphics.Rect
|
|||
import android.graphics.Typeface
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.appcompat.widget.AppCompatImageButton
|
||||
|
||||
class CountImageButton : ImageButton{
|
||||
class CountImageButton : AppCompatImageButton {
|
||||
|
||||
constructor(context : Context) : super(context) {
|
||||
init(context)
|
||||
|
@ -44,11 +44,11 @@ class CountImageButton : ImageButton{
|
|||
invalidate()
|
||||
}
|
||||
|
||||
var compoundPadding = 0
|
||||
var paddingRightOriginal = 0
|
||||
var paddingRightWithText = 0
|
||||
val textBounds = Rect()
|
||||
var textWidth = 0
|
||||
private var compoundPadding = 0
|
||||
private var paddingRightOriginal = 0
|
||||
private var paddingRightWithText = 0
|
||||
private val textBounds = Rect()
|
||||
private var textWidth = 0
|
||||
|
||||
override fun setPadding(left : Int, top : Int, right : Int, bottom : Int) {
|
||||
paddingRightOriginal = right
|
||||
|
|
|
@ -3,114 +3,110 @@ package jp.juggler.util
|
|||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.OpenableColumns
|
||||
import android.webkit.MimeTypeMap
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
internal object StorageUtils{
|
||||
|
||||
private val log = LogCategory("StorageUtils")
|
||||
|
||||
private const val PATH_TREE = "tree"
|
||||
private const val PATH_DOCUMENT = "document"
|
||||
|
||||
internal class FileInfo(any_uri : String?) {
|
||||
|
||||
var uri : Uri? = null
|
||||
private var mime_type : String? = null
|
||||
|
||||
init {
|
||||
if(any_uri != null) {
|
||||
uri = if(any_uri.startsWith("/")) {
|
||||
Uri.fromFile(File(any_uri))
|
||||
} else {
|
||||
any_uri.toUri()
|
||||
}
|
||||
val ext = MimeTypeMap.getFileExtensionFromUrl(any_uri)
|
||||
if(ext != null) {
|
||||
mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecondaryStorageVolumesMap(context : Context) : Map<String, String> {
|
||||
val result = HashMap<String, String>()
|
||||
try {
|
||||
val sm = context.applicationContext.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
|
||||
if(sm == null) {
|
||||
log.e("can't get StorageManager")
|
||||
} else {
|
||||
|
||||
// SDカードスロットのある7.0端末が手元にないから検証できない
|
||||
// if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
|
||||
// for(StorageVolume volume : sm.getStorageVolumes() ){
|
||||
// // String path = volume.getPath();
|
||||
// String state = volume.getState();
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
val getVolumeList = sm.javaClass.getMethod("getVolumeList")
|
||||
val volumes = getVolumeList.invoke(sm)
|
||||
log.d("volumes type=%s", volumes.javaClass)
|
||||
|
||||
if(volumes is ArrayList<*>) {
|
||||
//
|
||||
for(volume in volumes) {
|
||||
val volume_clazz = volume.javaClass
|
||||
|
||||
val path = volume_clazz.getMethod("getPath").invoke(volume) as? String
|
||||
val state = volume_clazz.getMethod("getState").invoke(volume) as? String
|
||||
if(path != null && state == "mounted") {
|
||||
//
|
||||
val isPrimary = volume_clazz.getMethod("isPrimary").invoke(volume) as? Boolean
|
||||
if(isPrimary == true) result["primary"] = path
|
||||
//
|
||||
val uuid = volume_clazz.getMethod("getUuid").invoke(volume) as? String
|
||||
if(uuid != null) result[uuid] = path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun isExternalStorageDocument(uri : Uri) : Boolean {
|
||||
return "com.android.externalstorage.documents" == uri.authority
|
||||
}
|
||||
|
||||
private fun getDocumentId(documentUri : Uri) : String {
|
||||
val paths = documentUri.pathSegments
|
||||
if(paths.size >= 2 && PATH_DOCUMENT == paths[0]) {
|
||||
// document
|
||||
return paths[1]
|
||||
}
|
||||
if(paths.size >= 4 && PATH_TREE == paths[0]
|
||||
&& PATH_DOCUMENT == paths[2]) {
|
||||
// document in tree
|
||||
return paths[3]
|
||||
}
|
||||
if(paths.size >= 2 && PATH_TREE == paths[0]) {
|
||||
// tree
|
||||
return paths[1]
|
||||
}
|
||||
throw IllegalArgumentException("Invalid URI: $documentUri")
|
||||
}
|
||||
|
||||
// internal object StorageUtils{
|
||||
//
|
||||
// private val log = LogCategory("StorageUtils")
|
||||
//
|
||||
// private const val PATH_TREE = "tree"
|
||||
// private const val PATH_DOCUMENT = "document"
|
||||
//
|
||||
// internal class FileInfo(any_uri : String?) {
|
||||
//
|
||||
// var uri : Uri? = null
|
||||
// private var mime_type : String? = null
|
||||
//
|
||||
// init {
|
||||
// if(any_uri != null) {
|
||||
// uri = if(any_uri.startsWith("/")) {
|
||||
// Uri.fromFile(File(any_uri))
|
||||
// } else {
|
||||
// any_uri.toUri()
|
||||
// }
|
||||
// val ext = MimeTypeMap.getFileExtensionFromUrl(any_uri)
|
||||
// if(ext != null) {
|
||||
// mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase(Locale.JAPAN))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun getSecondaryStorageVolumesMap(context : Context) : Map<String, String> {
|
||||
// val result = HashMap<String, String>()
|
||||
// try {
|
||||
// val sm = context.applicationContext.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
|
||||
// if(sm == null) {
|
||||
// log.e("can't get StorageManager")
|
||||
// } else {
|
||||
//
|
||||
// // SDカードスロットのある7.0端末が手元にないから検証できない
|
||||
// // if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
|
||||
// // for(StorageVolume volume : sm.getStorageVolumes() ){
|
||||
// // // String path = volume.getPath();
|
||||
// // String state = volume.getState();
|
||||
// //
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
// val getVolumeList = sm.javaClass.getMethod("getVolumeList")
|
||||
// val volumes = getVolumeList.invoke(sm)
|
||||
// log.d("volumes type=%s", volumes.javaClass)
|
||||
//
|
||||
// if(volumes is ArrayList<*>) {
|
||||
// //
|
||||
// for(volume in volumes) {
|
||||
// val volume_clazz = volume.javaClass
|
||||
//
|
||||
// val path = volume_clazz.getMethod("getPath").invoke(volume) as? String
|
||||
// val state = volume_clazz.getMethod("getState").invoke(volume) as? String
|
||||
// if(path != null && state == "mounted") {
|
||||
// //
|
||||
// val isPrimary = volume_clazz.getMethod("isPrimary").invoke(volume) as? Boolean
|
||||
// if(isPrimary == true) result["primary"] = path
|
||||
// //
|
||||
// val uuid = volume_clazz.getMethod("getUuid").invoke(volume) as? String
|
||||
// if(uuid != null) result[uuid] = path
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch(ex : Throwable) {
|
||||
// log.trace(ex)
|
||||
// }
|
||||
//
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// private fun isExternalStorageDocument(uri : Uri) : Boolean {
|
||||
// return "com.android.externalstorage.documents" == uri.authority
|
||||
// }
|
||||
//
|
||||
// private fun getDocumentId(documentUri : Uri) : String {
|
||||
// val paths = documentUri.pathSegments
|
||||
// if(paths.size >= 2 && PATH_DOCUMENT == paths[0]) {
|
||||
// // document
|
||||
// return paths[1]
|
||||
// }
|
||||
// if(paths.size >= 4 && PATH_TREE == paths[0]
|
||||
// && PATH_DOCUMENT == paths[2]) {
|
||||
// // document in tree
|
||||
// return paths[3]
|
||||
// }
|
||||
// if(paths.size >= 2 && PATH_TREE == paths[0]) {
|
||||
// // tree
|
||||
// return paths[1]
|
||||
// }
|
||||
// throw IllegalArgumentException("Invalid URI: $documentUri")
|
||||
// }
|
||||
|
||||
// fun getFile(context : Context, path : String) : File? {
|
||||
// try {
|
||||
// if(path.startsWith("/")) return File(path)
|
||||
|
@ -163,20 +159,23 @@ internal object StorageUtils{
|
|||
//
|
||||
// return null
|
||||
// }
|
||||
|
||||
internal val mimeTypeExMap : HashMap<String, String> by lazy {
|
||||
val map = HashMap<String, String>()
|
||||
map["BDM"] = "application/vnd.syncml.dm+wbxml"
|
||||
map["DAT"] = ""
|
||||
map["TID"] = ""
|
||||
map["js"] = "text/javascript"
|
||||
map["sh"] = "application/x-sh"
|
||||
map["lua"] = "text/x-lua"
|
||||
map
|
||||
}
|
||||
|
||||
|
||||
const val MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream"
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
//}
|
||||
|
||||
private const val MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream"
|
||||
|
||||
private val mimeTypeExMap : HashMap<String, String> by lazy {
|
||||
val map = HashMap<String, String>()
|
||||
map["BDM"] = "application/vnd.syncml.dm+wbxml"
|
||||
map["DAT"] = ""
|
||||
map["TID"] = ""
|
||||
map["js"] = "text/javascript"
|
||||
map["sh"] = "application/x-sh"
|
||||
map["lua"] = "text/x-lua"
|
||||
map
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
|
@ -190,7 +189,7 @@ fun getMimeType(log : LogCategory?, src : String) : String {
|
|||
if(mime_type?.isNotEmpty() == true) return mime_type
|
||||
|
||||
//
|
||||
mime_type = StorageUtils.mimeTypeExMap[ext]
|
||||
mime_type = mimeTypeExMap[ext]
|
||||
if(mime_type?.isNotEmpty() == true) return mime_type
|
||||
|
||||
// 戻り値が空文字列の場合とnullの場合があり、空文字列の場合は既知なのでログ出力しない
|
||||
|
@ -199,10 +198,9 @@ fun getMimeType(log : LogCategory?, src : String) : String {
|
|||
log.w("getMimeType(): unknown file extension '%s'", ext)
|
||||
}
|
||||
}
|
||||
return StorageUtils.MIME_TYPE_APPLICATION_OCTET_STREAM
|
||||
return MIME_TYPE_APPLICATION_OCTET_STREAM
|
||||
}
|
||||
|
||||
|
||||
fun getDocumentName(contentResolver : ContentResolver, uri : Uri) : String {
|
||||
val errorName = "no_name"
|
||||
return contentResolver.query(uri, null, null, null, null, null)
|
||||
|
@ -288,7 +286,6 @@ fun intentGetContent(
|
|||
return Intent.createChooser(intent, caption)
|
||||
}
|
||||
|
||||
|
||||
data class GetContentResultEntry(
|
||||
val uri : Uri,
|
||||
val mimeType : String? = null,
|
||||
|
|
|
@ -726,6 +726,7 @@
|
|||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/max_toot_chars"
|
||||
android:labelFor="@+id/etMaxTootChars"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/svContent"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -8,24 +7,20 @@
|
|||
|
||||
android:clipToPadding="false"
|
||||
android:fillViewport="true"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
>
|
||||
android:paddingBottom="12dp"
|
||||
android:scrollbarStyle="outsideOverlay">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/column_header"
|
||||
/>
|
||||
android:text="@string/column_header" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -35,11 +30,11 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="3dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="3dp"
|
||||
>
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="3dp"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivColumnHeader"
|
||||
|
@ -47,25 +42,21 @@
|
|||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="?attr/btn_federate_tl"
|
||||
/>
|
||||
tools:src="@drawable/ic_bike" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvColumnName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
tools:text="@string/federate_timeline"
|
||||
/>
|
||||
tools:text="@string/federate_timeline" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label_indent1"
|
||||
android:text="@string/background_color"
|
||||
/>
|
||||
<TextView
|
||||
style="@style/setting_row_label_indent1"
|
||||
android:text="@string/background_color" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -73,23 +64,19 @@
|
|||
android:id="@+id/btnHeaderBackgroundEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
/>
|
||||
android:text="@string/edit" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnHeaderBackgroundReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
/>
|
||||
android:text="@string/reset" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label_indent1"
|
||||
android:text="@string/foreground_color"
|
||||
/>
|
||||
<TextView
|
||||
style="@style/setting_row_label_indent1"
|
||||
android:text="@string/foreground_color" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -98,70 +85,63 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnHeaderTextReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/column"
|
||||
/>
|
||||
android:text="@string/column" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flColumnBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivColumnBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSampleAcct"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/colorTimeSmall"
|
||||
android:textSize="12sp"
|
||||
android:text="@string/acct_sample"
|
||||
android:id="@+id/tvSampleAcct"
|
||||
/>
|
||||
android:textColor="?attr/colorTimeSmall"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvSampleContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:textColor="?attr/colorContentText"
|
||||
android:gravity="start"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:text="@string/content_sample"
|
||||
android:id="@+id/tvSampleContent"
|
||||
/>
|
||||
android:textColor="?attr/colorContentText" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -171,8 +151,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color"
|
||||
/>
|
||||
android:text="@string/background_color" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
@ -182,17 +161,14 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnColumnBackgroundColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -200,8 +176,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_image"
|
||||
/>
|
||||
android:text="@string/background_image" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
@ -211,16 +186,14 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pick_image"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnColumnBackgroundImageReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -229,21 +202,18 @@
|
|||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:labelFor="@+id/etAlpha"
|
||||
android:text="@string/background_image_alpha"
|
||||
/>
|
||||
android:text="@string/background_image_alpha" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/setting_row_form"
|
||||
android:layout_height="48dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical"
|
||||
>
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
/>
|
||||
android:layout_height="48dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAlpha"
|
||||
|
@ -251,28 +221,25 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:digits="0123456789.,"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="numberDecimal"
|
||||
android:maxLines="1"
|
||||
android:minLines="1"
|
||||
android:minWidth="64dp"
|
||||
/>
|
||||
android:minLines="1" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/sbColumnBackgroundAlpha"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:layout_height="48dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingStart="32dp"
|
||||
/>
|
||||
android:paddingEnd="32dp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/acct_color"
|
||||
/>
|
||||
android:text="@string/acct_color" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
@ -282,27 +249,22 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAcctColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/content_color"
|
||||
/>
|
||||
android:text="@string/content_color" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
@ -312,19 +274,17 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnContentColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/svContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -15,42 +13,38 @@
|
|||
|
||||
android:clipToPadding="false"
|
||||
android:fillViewport="true"
|
||||
android:paddingBottom="128dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="128dp"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
tools:ignore="TooManyViews"
|
||||
>
|
||||
tools:ignore="TooManyViews">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:labelFor="@+id/etPhrase"
|
||||
android:text="@string/filter_phrase"
|
||||
/>
|
||||
android:text="@string/filter_phrase" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPhrase"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/filter_context"
|
||||
/>
|
||||
android:text="@string/filter_context" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -58,8 +52,7 @@
|
|||
android:id="@+id/cbContextHome"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:checked="true"
|
||||
android:text="@string/filter_home"
|
||||
/>
|
||||
android:text="@string/filter_home" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -69,8 +62,7 @@
|
|||
android:id="@+id/cbContextNotification"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:checked="true"
|
||||
android:text="@string/filter_notification"
|
||||
/>
|
||||
android:text="@string/filter_notification" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -80,8 +72,7 @@
|
|||
android:id="@+id/cbContextPublic"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:checked="true"
|
||||
android:text="@string/filter_public"
|
||||
/>
|
||||
android:text="@string/filter_public" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -91,60 +82,55 @@
|
|||
android:id="@+id/cbContextThread"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:checked="true"
|
||||
android:text="@string/filter_thread"
|
||||
/>
|
||||
android:text="@string/filter_thread" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/filter_options"
|
||||
/>
|
||||
android:text="@string/filter_options" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbFilterIrreversible"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:text="@string/filter_irreversible_long"
|
||||
/>
|
||||
android:text="@string/filter_irreversible_long" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbFilterWordMatch"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:text="@string/filter_word_match_long"
|
||||
/>
|
||||
android:text="@string/filter_word_match_long" />
|
||||
|
||||
</LinearLayout>
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/filter_expires_at"
|
||||
/>
|
||||
android:text="@string/filter_expires_at" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvExpire"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spExpire"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -157,6 +143,5 @@
|
|||
android:id="@+id/btnSave"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/save"
|
||||
/>
|
||||
android:text="@string/save" />
|
||||
</LinearLayout>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/llContent"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -14,81 +13,72 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:text="@string/preview"
|
||||
/>
|
||||
android:paddingEnd="12dp"
|
||||
android:text="@string/preview" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:textSize="20sp"
|
||||
tools:text="preview..."
|
||||
/>
|
||||
tools:text="preview..." />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:fillViewport="true"
|
||||
>
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
>
|
||||
android:padding="12dp">
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/acct"
|
||||
/>
|
||||
android:text="@string/acct" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAcct"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:labelFor="@+id/etNickname"
|
||||
android:text="@string/nickname"
|
||||
/>
|
||||
android:text="@string/nickname" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etNickname"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
|
||||
/>
|
||||
android:maxLines="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/text_color"
|
||||
/>
|
||||
android:text="@string/text_color" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -97,25 +87,22 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTextColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color"
|
||||
/>
|
||||
android:text="@string/background_color" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -124,32 +111,28 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llNotificationSound"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/llNotificationSound"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/notification_sound_before_oreo"
|
||||
/>
|
||||
android:text="@string/notification_sound_before_oreo" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -158,20 +141,18 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNotificationSoundReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
@ -181,15 +162,13 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/nickname_applied_after_reload"
|
||||
android:textSize="12sp"
|
||||
/>
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:measureWithLargestChild="true"
|
||||
>
|
||||
android:measureWithLargestChild="true">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSave"
|
||||
|
@ -198,8 +177,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/save"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDiscard"
|
||||
|
@ -208,7 +186,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/discard"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -1,12 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/viewRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
|
@ -18,8 +16,7 @@
|
|||
android:fadingEdgeLength="20dp"
|
||||
android:fillViewport="true"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
>
|
||||
android:scrollbarStyle="outsideOverlay">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llContent"
|
||||
|
@ -29,8 +26,7 @@
|
|||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="320dp"
|
||||
>
|
||||
android:paddingBottom="320dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llReply"
|
||||
|
@ -39,21 +35,18 @@
|
|||
android:layout_marginBottom="4dp"
|
||||
android:background="?attr/colorReplyBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="6dp"
|
||||
>
|
||||
android:padding="6dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reply_to_this_status"
|
||||
/>
|
||||
android:text="@string/reply_to_this_status" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
android:id="@+id/ivReply"
|
||||
|
@ -61,16 +54,14 @@
|
|||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvReplyTo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
/>
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnRemoveReply"
|
||||
|
@ -81,31 +72,27 @@
|
|||
android:contentDescription="@string/delete"
|
||||
android:gravity="center_vertical"
|
||||
android:src="@drawable/ic_close"
|
||||
android:tint="?attr/colorVectorDrawable"
|
||||
/>
|
||||
android:tint="?attr/colorVectorDrawable" />
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbQuoteRenote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/make_quote_renote"
|
||||
/>
|
||||
android:text="@string/make_quote_renote" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/post_from"
|
||||
/>
|
||||
android:text="@string/post_from" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAccount"
|
||||
|
@ -116,8 +103,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -127,16 +113,14 @@
|
|||
android:layout_marginTop="4dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
android:id="@+id/ivMedia1"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
android:id="@+id/ivMedia2"
|
||||
|
@ -144,8 +128,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
android:id="@+id/ivMedia3"
|
||||
|
@ -153,8 +136,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
android:id="@+id/ivMedia4"
|
||||
|
@ -162,8 +144,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbNSFW"
|
||||
|
@ -171,8 +152,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/nsfw"
|
||||
/>
|
||||
android:text="@string/nsfw" />
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
|
@ -180,30 +160,26 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/content_warning"
|
||||
/>
|
||||
android:text="@string/content_warning" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
android:background="?attr/colorPostFormBackground">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
android:id="@+id/etContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/content_warning_hint"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:inputType="text" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
|
@ -211,8 +187,7 @@
|
|||
android:layout_gravity="bottom"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/status"
|
||||
/>
|
||||
android:text="@string/status" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnFeaturedTag"
|
||||
|
@ -253,8 +228,7 @@
|
|||
android:gravity="start|top"
|
||||
android:hint="@string/content_hint"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="5"
|
||||
/>
|
||||
android:minLines="5" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -262,14 +236,12 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/scheduled_status"
|
||||
/>
|
||||
android:text="@string/scheduled_status" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSchedule"
|
||||
|
@ -277,8 +249,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
/>
|
||||
android:gravity="center" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibSchedule"
|
||||
|
@ -309,36 +280,31 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/make_enquete"
|
||||
/>
|
||||
android:text="@string/make_enquete" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llEnquete"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/choice1"
|
||||
/>
|
||||
android:text="@string/choice1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
android:background="?attr/colorPostFormBackground">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
android:id="@+id/etChoice1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|top"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:inputType="text" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -346,22 +312,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/choice2"
|
||||
/>
|
||||
android:text="@string/choice2" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
android:background="?attr/colorPostFormBackground">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
android:id="@+id/etChoice2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|top"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:inputType="text" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -369,22 +332,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/choice3"
|
||||
/>
|
||||
android:text="@string/choice3" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
android:background="?attr/colorPostFormBackground">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
android:id="@+id/etChoice3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|top"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:inputType="text" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -392,22 +352,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/choice4"
|
||||
/>
|
||||
android:text="@string/choice4" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
android:background="?attr/colorPostFormBackground">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
android:id="@+id/etChoice4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|top"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:inputType="text" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -416,31 +373,27 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/allow_multiple_choice"
|
||||
/>
|
||||
android:text="@string/allow_multiple_choice" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbHideTotals"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/hide_totals"
|
||||
/>
|
||||
android:text="@string/hide_totals" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llExpire"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/expiration"
|
||||
/>
|
||||
android:text="@string/expiration" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etExpireDays"
|
||||
|
@ -450,23 +403,21 @@
|
|||
android:inputType="numberDecimal"
|
||||
android:minWidth="48dp"
|
||||
android:text="1"
|
||||
tools:ignore="Autofill,HardcodedText"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor"
|
||||
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/poll_expire_days"
|
||||
/>
|
||||
android:text="@string/poll_expire_days" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/plus"
|
||||
/>
|
||||
android:text="@string/plus" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etExpireHours"
|
||||
|
@ -475,22 +426,19 @@
|
|||
android:gravity="center"
|
||||
android:inputType="numberDecimal"
|
||||
android:minWidth="48dp"
|
||||
tools:ignore="Autofill,HardcodedText"
|
||||
/>
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/poll_expire_hours"
|
||||
/>
|
||||
android:text="@string/poll_expire_hours" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/plus"
|
||||
/>
|
||||
android:text="@string/plus" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etExpireMinutes"
|
||||
|
@ -499,14 +447,12 @@
|
|||
android:gravity="center"
|
||||
android:inputType="numberDecimal"
|
||||
android:minWidth="48dp"
|
||||
tools:ignore="Autofill,HardcodedText"
|
||||
/>
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/poll_expire_minutes"
|
||||
/>
|
||||
android:text="@string/poll_expire_minutes" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -521,8 +467,7 @@
|
|||
android:layout_height="48dp"
|
||||
android:background="?attr/colorStatusButtonsPopupBg"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnAttachment"
|
||||
|
@ -531,8 +476,7 @@
|
|||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/media_attachment"
|
||||
android:src="@drawable/ic_clip"
|
||||
android:tint="?attr/colorVectorDrawable"
|
||||
/>
|
||||
android:tint="?attr/colorVectorDrawable" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnVisibility"
|
||||
|
@ -544,8 +488,7 @@
|
|||
android:minWidth="48dp"
|
||||
android:minHeight="48dp"
|
||||
android:tint="?attr/colorVectorDrawable"
|
||||
tools:src="@drawable/ic_public"
|
||||
/>
|
||||
tools:src="@drawable/ic_public" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPlugin"
|
||||
|
@ -555,8 +498,7 @@
|
|||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/plugin"
|
||||
android:src="@drawable/ic_extension"
|
||||
android:tint="?attr/colorVectorDrawable"
|
||||
/>
|
||||
android:tint="?attr/colorVectorDrawable" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnMore"
|
||||
|
@ -573,8 +515,7 @@
|
|||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCharCount"
|
||||
|
@ -585,8 +526,7 @@
|
|||
android:gravity="end|center_vertical"
|
||||
android:minWidth="32dp"
|
||||
tools:text="-500"
|
||||
tools:textColor="#f00"
|
||||
/>
|
||||
tools:textColor="#f00" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPost"
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
android:id="@+id/btnCopy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/copy"
|
||||
android:text="@string/copy_st"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/reason_create_account"
|
||||
android:id="@+id/tvReasonCaption"
|
||||
android:labelFor="@+id/etReason"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
|
|
|
@ -1,190 +1,177 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<jp.juggler.subwaytooter.view.MaxHeightScrollView
|
||||
android:id="@+id/llColumnSetting"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadeScrollbars="false"
|
||||
app:maxHeight="240dp"
|
||||
>
|
||||
app:maxHeight="240dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
>
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="3dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/visibility"
|
||||
/>
|
||||
android:text="@string/visibility" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnVisibility"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/btnVisibility"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fixed_phrase"
|
||||
android:layout_marginTop="6dp"
|
||||
/>
|
||||
android:text="@string/fixed_phrase" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etText0"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_weight="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
/>
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnText0"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/input"
|
||||
/>
|
||||
android:text="@string/input" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etText1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_weight="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
/>
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnText1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/input"
|
||||
/>
|
||||
android:text="@string/input" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etText2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_weight="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
/>
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnText2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/input"
|
||||
/>
|
||||
android:text="@string/input" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etText3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_weight="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
/>
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnText3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/input"
|
||||
/>
|
||||
android:text="@string/input" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etText4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="text"
|
||||
android:layout_marginEnd="6dp"
|
||||
/>
|
||||
android:layout_weight="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnText4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/input"
|
||||
/>
|
||||
android:text="@string/input" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etText5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="text"
|
||||
android:layout_marginEnd="6dp"
|
||||
/>
|
||||
android:layout_weight="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnText5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/input"
|
||||
/>
|
||||
android:text="@string/input" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</jp.juggler.subwaytooter.view.MaxHeightScrollView>
|
||||
|
@ -193,8 +180,7 @@
|
|||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCancel"
|
||||
|
@ -202,7 +188,6 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/close"
|
||||
/>
|
||||
android:text="@string/close" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -1,37 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCaption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:id="@+id/tvCaption"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:labelFor="@+id/etInput"
|
||||
/>
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="text"
|
||||
/>
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCancel"
|
||||
|
@ -39,8 +37,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/cancel"
|
||||
/>
|
||||
android:text="@string/cancel" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOk"
|
||||
|
@ -48,7 +45,6 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/ok"
|
||||
/>
|
||||
android:text="@string/ok" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -97,7 +97,7 @@
|
|||
<string name="color_and_background">اللون والخلفية…</string>
|
||||
<string name="column_background">خلفية العمود</string>
|
||||
<string name="foreground_color">اللون الأمامي</string>
|
||||
<string name="copy">نسخ</string>
|
||||
<string name="copy_st">نسخ</string>
|
||||
<string name="search_web">البحث على الويب</string>
|
||||
<string name="muted_word">الكلمات المحظورة</string>
|
||||
<string name="word_was_muted">تم حظر الكلمة.</string>
|
||||
|
|
|
@ -242,7 +242,7 @@
|
|||
<string name="image">Delwedd</string>
|
||||
<string name="column_header">Pennawd y golofn</string>
|
||||
<string name="foreground_color">Lliw y blaendir</string>
|
||||
<string name="copy">Copïo</string>
|
||||
<string name="copy_st">Copïo</string>
|
||||
<string name="send">Anfon</string>
|
||||
<string name="mute_word">Gwahardd gair</string>
|
||||
<string name="select_and_copy">Dewis a chopïo</string>
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
<string name="conversation">conversation</string>
|
||||
<string name="conversation_around">Conversation du statut: %1$s</string>
|
||||
<string name="conversation_view">Visualiser la conversation</string>
|
||||
<string name="copy">Copier</string>
|
||||
<string name="copy_st">Copier</string>
|
||||
<string name="copy_complete">Presse-papiers mis à jour.</string>
|
||||
<string name="default_status_visibility">Visibilité par défaut du pouet</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
|
|
|
@ -364,7 +364,7 @@
|
|||
<string name="column_header">כותר העמודה</string>
|
||||
<string name="foreground_color">צבע החזית</string>
|
||||
<string name="hashtag_and_visibility_not_match">הודעה זו מכילה תגיות , אך נראות ההודעה אינה ציבורית. בדרך כלל ניתן לחפש תגיות רק בהודעות ציבוריות. האם אתה בטוח\?</string>
|
||||
<string name="copy">העתק</string>
|
||||
<string name="copy_st">העתק</string>
|
||||
<string name="send">שלח</string>
|
||||
<string name="mute_word">אסור מילה</string>
|
||||
<string name="select_and_copy">בחר והעתק</string>
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
<string name="conversation_around">会話の流れ(id:%1$s)</string>
|
||||
<string name="conversation_from_another_account">別アカウントで会話の流れ</string>
|
||||
<string name="conversation_view">会話ビュー</string>
|
||||
<string name="copy">コピー</string>
|
||||
<string name="copy_st">コピー</string>
|
||||
<string name="copy_complete">クリップボードにコピーしました</string>
|
||||
<string name="copy_url">URLをクリップボードにコピー</string>
|
||||
<string name="default_status_visibility">投稿の公開範囲の既定値</string>
|
||||
|
|
|
@ -305,7 +305,7 @@
|
|||
<string name="column_header">칼럼 헤더</string>
|
||||
<string name="foreground_color">전면색</string>
|
||||
<string name="hashtag_and_visibility_not_match">이 메시지는 해시태그를 포함하고 있지만 공개 범위가 전체 공개가 아닙니다. 보통 해시태그는 공개 메시지에서만 검색 가능합니다. 계속하시겠습니까\?</string>
|
||||
<string name="copy">복사</string>
|
||||
<string name="copy_st">복사</string>
|
||||
<string name="send">전송</string>
|
||||
<string name="mute_word">금지 단어</string>
|
||||
<string name="select_and_copy">선택과 복사</string>
|
||||
|
|
|
@ -255,7 +255,7 @@
|
|||
<string name="pick_images">Velg bilde(r)…</string>
|
||||
<string name="image">Bilde</string>
|
||||
<string name="foreground_color">Forgrunnsfarge</string>
|
||||
<string name="copy">Kopier</string>
|
||||
<string name="copy_st">Kopier</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="mute_word">Bannlys ord</string>
|
||||
<string name="select_and_copy">Velg og kopier</string>
|
||||
|
|
|
@ -311,7 +311,7 @@
|
|||
<string name="column_header">Column header</string>
|
||||
<string name="foreground_color">Foreground color</string>
|
||||
<string name="hashtag_and_visibility_not_match">this message contains hashtags, but message visibility is not public. normally hashtags are searchable only in public messages. Are you sure?</string>
|
||||
<string name="copy">Copy</string>
|
||||
<string name="copy_st">Copy</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="mute_word">Ban word</string>
|
||||
<string name="select_and_copy">Select and copy</string>
|
||||
|
|
|
@ -21,90 +21,93 @@ import android.graphics.Bitmap.Config;
|
|||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* This drawable will draw a simple white and gray chessboard pattern.
|
||||
* It's the pattern you will often see as a background behind a partly transparent image in many applications.
|
||||
*/
|
||||
class AlphaPatternDrawable extends Drawable {
|
||||
|
||||
private int rectangleSize = 10;
|
||||
|
||||
private Paint paint = new Paint();
|
||||
private Paint paintWhite = new Paint();
|
||||
private Paint paintGray = new Paint();
|
||||
|
||||
private int numRectanglesHorizontal;
|
||||
private int numRectanglesVertical;
|
||||
|
||||
/**
|
||||
* Bitmap in which the pattern will be cached.
|
||||
* This is so the pattern will not have to be recreated each time draw() gets called.
|
||||
* Because recreating the pattern i rather expensive. I will only be recreated if the size changes.
|
||||
*/
|
||||
private Bitmap bitmap;
|
||||
|
||||
AlphaPatternDrawable(int rectangleSize) {
|
||||
this.rectangleSize = rectangleSize;
|
||||
paintWhite.setColor(0xFFFFFFFF);
|
||||
paintGray.setColor(0xFFCBCBCB);
|
||||
}
|
||||
|
||||
@Override public void draw(Canvas canvas) {
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
canvas.drawBitmap(bitmap, null, getBounds(), paint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public void setAlpha(int alpha) {
|
||||
throw new UnsupportedOperationException("Alpha is not supported by this drawable.");
|
||||
}
|
||||
|
||||
@Override public void setColorFilter(ColorFilter cf) {
|
||||
throw new UnsupportedOperationException("ColorFilter is not supported by this drawable.");
|
||||
}
|
||||
|
||||
@Override protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
int height = bounds.height();
|
||||
int width = bounds.width();
|
||||
numRectanglesHorizontal = (int) Math.ceil((width / rectangleSize));
|
||||
numRectanglesVertical = (int) Math.ceil(height / rectangleSize);
|
||||
generatePatternBitmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on.
|
||||
* We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds
|
||||
*/
|
||||
private void generatePatternBitmap() {
|
||||
if (getBounds().width() <= 0 || getBounds().height() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
Rect r = new Rect();
|
||||
boolean verticalStartWhite = true;
|
||||
for (int i = 0; i <= numRectanglesVertical; i++) {
|
||||
boolean isWhite = verticalStartWhite;
|
||||
for (int j = 0; j <= numRectanglesHorizontal; j++) {
|
||||
r.top = i * rectangleSize;
|
||||
r.left = j * rectangleSize;
|
||||
r.bottom = r.top + rectangleSize;
|
||||
r.right = r.left + rectangleSize;
|
||||
canvas.drawRect(r, isWhite ? paintWhite : paintGray);
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
verticalStartWhite = !verticalStartWhite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int rectangleSize;
|
||||
|
||||
private Paint paint = new Paint();
|
||||
private Paint paintWhite = new Paint();
|
||||
private Paint paintGray = new Paint();
|
||||
|
||||
private int numRectanglesHorizontal;
|
||||
private int numRectanglesVertical;
|
||||
|
||||
/**
|
||||
* Bitmap in which the pattern will be cached.
|
||||
* This is so the pattern will not have to be recreated each time draw() gets called.
|
||||
* Because recreating the pattern i rather expensive. I will only be recreated if the size changes.
|
||||
*/
|
||||
private Bitmap bitmap;
|
||||
|
||||
AlphaPatternDrawable( int rectangleSize ){
|
||||
this.rectangleSize = rectangleSize;
|
||||
paintWhite.setColor( 0xFFFFFFFF );
|
||||
paintGray.setColor( 0xFFCBCBCB );
|
||||
}
|
||||
|
||||
@Override public void draw( @NonNull Canvas canvas ){
|
||||
if( bitmap != null && ! bitmap.isRecycled() ){
|
||||
canvas.drawBitmap( bitmap, null, getBounds(), paint );
|
||||
}
|
||||
}
|
||||
|
||||
@Override public int getOpacity(){
|
||||
return PixelFormat.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override public void setAlpha( int alpha ){
|
||||
throw new UnsupportedOperationException( "Alpha is not supported by this drawable." );
|
||||
}
|
||||
|
||||
@Override public void setColorFilter( ColorFilter cf ){
|
||||
throw new UnsupportedOperationException( "ColorFilter is not supported by this drawable." );
|
||||
}
|
||||
|
||||
@Override protected void onBoundsChange( Rect bounds ){
|
||||
super.onBoundsChange( bounds );
|
||||
int height = bounds.height();
|
||||
int width = bounds.width();
|
||||
numRectanglesHorizontal = (int) Math.ceil( width / (float) rectangleSize );
|
||||
numRectanglesVertical = (int) Math.ceil( height / (float) rectangleSize );
|
||||
generatePatternBitmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on.
|
||||
* We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds
|
||||
*/
|
||||
private void generatePatternBitmap(){
|
||||
if( getBounds().width() <= 0 || getBounds().height() <= 0 ){
|
||||
return;
|
||||
}
|
||||
|
||||
bitmap = Bitmap.createBitmap( getBounds().width(), getBounds().height(), Config.ARGB_8888 );
|
||||
Canvas canvas = new Canvas( bitmap );
|
||||
|
||||
Rect r = new Rect();
|
||||
boolean verticalStartWhite = true;
|
||||
for( int i = 0 ; i <= numRectanglesVertical ; i++ ){
|
||||
boolean isWhite = verticalStartWhite;
|
||||
for( int j = 0 ; j <= numRectanglesHorizontal ; j++ ){
|
||||
r.top = i * rectangleSize;
|
||||
r.left = j * rectangleSize;
|
||||
r.bottom = r.top + rectangleSize;
|
||||
r.right = r.left + rectangleSize;
|
||||
canvas.drawRect( r, isWhite ? paintWhite : paintGray );
|
||||
isWhite = ! isWhite;
|
||||
}
|
||||
verticalStartWhite = ! verticalStartWhite;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,97 +1,91 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPickerView
|
||||
android:id="@id/cpv_color_picker_view"
|
||||
style="@style/cpv_ColorPickerViewStyle"
|
||||
android:padding="16dp"/>
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_old"
|
||||
android:layout_width="@dimen/cpv_dialog_preview_width"
|
||||
android:layout_height="@dimen/cpv_dialog_preview_height"
|
||||
app:cpv_colorShape="square"/>
|
||||
<com.jrummyapps.android.colorpicker.ColorPickerView
|
||||
android:id="@id/cpv_color_picker_view"
|
||||
style="@style/cpv_ColorPickerViewStyle"
|
||||
android:padding="16dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cpv_arrow_right"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:src="@drawable/cpv_ic_arrow_right_black_24dp"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_new"
|
||||
android:layout_width="@dimen/cpv_dialog_preview_width"
|
||||
android:layout_height="@dimen/cpv_dialog_preview_height"
|
||||
app:cpv_colorShape="square"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:descendantFocusability="beforeDescendants"
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="#"
|
||||
android:typeface="monospace"
|
||||
tools:ignore="HardcodedText"
|
||||
android:labelFor="@+id/cpv_hex"
|
||||
/>
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cpv_hex"
|
||||
android:layout_width="110sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789ABCDEFabcdef"
|
||||
android:focusable="true"
|
||||
android:imeOptions="actionGo"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:maxLength="8"
|
||||
android:maxLines="1"
|
||||
android:typeface="monospace"
|
||||
tools:text="88888888"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_old"
|
||||
android:layout_width="@dimen/cpv_dialog_preview_width"
|
||||
android:layout_height="@dimen/cpv_dialog_preview_height"
|
||||
app:cpv_colorShape="square" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
<ImageView
|
||||
android:id="@+id/cpv_arrow_right"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:src="@drawable/cpv_ic_arrow_right_black_24dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</LinearLayout>
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_new"
|
||||
android:layout_width="@dimen/cpv_dialog_preview_width"
|
||||
android:layout_height="@dimen/cpv_dialog_preview_height"
|
||||
app:cpv_colorShape="square" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:descendantFocusability="beforeDescendants"
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/cpv_hex"
|
||||
android:text="#"
|
||||
android:typeface="monospace"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cpv_hex"
|
||||
android:layout_width="110sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789ABCDEFabcdef"
|
||||
android:focusable="true"
|
||||
android:imeOptions="actionGo"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:maxLength="8"
|
||||
android:maxLines="1"
|
||||
android:typeface="monospace"
|
||||
tools:text="88888888" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -1,24 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false">
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@+id/cpv_color_panel_view"
|
||||
android:layout_width="@dimen/cpv_item_size"
|
||||
android:layout_height="@dimen/cpv_item_size"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
app:cpv_colorShape="circle"/>
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@+id/cpv_color_panel_view"
|
||||
android:layout_width="@dimen/cpv_item_size"
|
||||
android:layout_height="@dimen/cpv_item_size"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
app:cpv_colorShape="circle"
|
||||
tools:ignore="KeyboardInaccessibleWidget" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cpv_color_image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"/>
|
||||
<ImageView
|
||||
android:id="@+id/cpv_color_image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
</FrameLayout>
|
|
@ -1,24 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false">
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@+id/cpv_color_panel_view"
|
||||
android:layout_width="@dimen/cpv_item_size"
|
||||
android:layout_height="@dimen/cpv_item_size"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
app:cpv_colorShape="square"/>
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@+id/cpv_color_panel_view"
|
||||
android:layout_width="@dimen/cpv_item_size"
|
||||
android:layout_height="@dimen/cpv_item_size"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
app:cpv_colorShape="square"
|
||||
tools:ignore="KeyboardInaccessibleWidget" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cpv_color_image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"/>
|
||||
<ImageView
|
||||
android:id="@+id/cpv_color_image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
</FrameLayout>
|
|
@ -1,49 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPickerView
|
||||
android:id="@id/cpv_color_picker_view"
|
||||
style="@style/cpv_ColorPickerViewStyle"
|
||||
android:padding="16dp"
|
||||
/>
|
||||
android:padding="16dp" />
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_old"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
app:cpv_colorShape="square"
|
||||
/>
|
||||
app:cpv_colorShape="square" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cpv_arrow_right"
|
||||
|
@ -53,16 +44,14 @@
|
|||
android:paddingLeft="3dp"
|
||||
android:paddingRight="3dp"
|
||||
android:src="@drawable/cpv_ic_arrow_right_black_24dp"
|
||||
tools:ignore="ContentDescription"
|
||||
/>
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_new"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
app:cpv_colorShape="square"
|
||||
/>
|
||||
app:cpv_colorShape="square" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -73,16 +62,14 @@
|
|||
android:focusableInTouchMode="true"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="#"
|
||||
android:typeface="monospace"
|
||||
tools:ignore="HardcodedText"
|
||||
/>
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cpv_hex"
|
||||
|
@ -91,13 +78,14 @@
|
|||
android:digits="0123456789ABCDEFabcdef"
|
||||
android:focusable="true"
|
||||
android:imeOptions="actionGo"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:maxLength="8"
|
||||
android:maxLines="1"
|
||||
android:minWidth="110dp"
|
||||
android:typeface="monospace"
|
||||
tools:text="88888888"
|
||||
/>
|
||||
tools:ignore="LabelFor"
|
||||
tools:text="88888888" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion target_sdk_version
|
||||
|
@ -23,4 +25,10 @@ android {
|
|||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'commons-io:commons-io:2.6'
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation "androidx.core:core-ktx:+"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
class ByteBufferInputStream extends InputStream {
|
||||
|
||||
private ByteBuffer mBuf;
|
||||
|
||||
public ByteBufferInputStream( ByteBuffer buf ) {
|
||||
mBuf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
if( ! mBuf.hasRemaining() ) {
|
||||
return - 1;
|
||||
}
|
||||
return mBuf.get() & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read( byte[] bytes, int off, int len ) {
|
||||
if( ! mBuf.hasRemaining() ) {
|
||||
return - 1;
|
||||
}
|
||||
|
||||
len = Math.min( len, mBuf.remaining() );
|
||||
mBuf.get( bytes, off, len );
|
||||
return len;
|
||||
}
|
||||
}
|
|
@ -14,18 +14,23 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
/**
|
||||
* The constants of the IFD ID defined in EXIF spec.
|
||||
*/
|
||||
public interface IfdId {
|
||||
public static final int TYPE_IFD_0 = 0;
|
||||
public static final int TYPE_IFD_1 = 1;
|
||||
public static final int TYPE_IFD_EXIF = 2;
|
||||
public static final int TYPE_IFD_INTEROPERABILITY = 3;
|
||||
public static final int TYPE_IFD_GPS = 4;
|
||||
/* This is used in ExifData to allocate enough IfdData */
|
||||
static final int TYPE_IFD_COUNT = 5;
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.min
|
||||
|
||||
internal class ByteBufferInputStream(private val mBuf : ByteBuffer) : InputStream() {
|
||||
|
||||
override fun read() : Int = when {
|
||||
! mBuf.hasRemaining() -> - 1
|
||||
else -> mBuf.get().toInt() and 0xFF
|
||||
}
|
||||
|
||||
override fun read(bytes : ByteArray, off : Int, len : Int) : Int {
|
||||
if(! mBuf.hasRemaining()) return - 1
|
||||
val willRead = min(len, mBuf.remaining())
|
||||
mBuf.get(bytes, off, willRead)
|
||||
return willRead
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
class CountedDataInputStream extends FilterInputStream {
|
||||
|
||||
// allocate a byte buffer for a long value;
|
||||
private final byte mByteArray[] = new byte[8];
|
||||
private final ByteBuffer mByteBuffer = ByteBuffer.wrap( mByteArray );
|
||||
private int mCount = 0;
|
||||
private int mEnd = 0;
|
||||
|
||||
protected CountedDataInputStream( InputStream in ) {
|
||||
super( in );
|
||||
}
|
||||
|
||||
public void setEnd( int end ) {
|
||||
mEnd = end;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
public int getReadByteCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read( byte[] b ) throws IOException {
|
||||
int r = in.read( b );
|
||||
mCount += ( r >= 0 ) ? r : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int r = in.read();
|
||||
mCount += ( r >= 0 ) ? 1 : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read( byte[] b, int off, int len ) throws IOException {
|
||||
int r = in.read( b, off, len );
|
||||
mCount += ( r >= 0 ) ? r : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip( long length ) throws IOException {
|
||||
long skip = in.skip( length );
|
||||
mCount += skip;
|
||||
return skip;
|
||||
}
|
||||
|
||||
public void skipTo( long target ) throws IOException {
|
||||
long cur = mCount;
|
||||
long diff = target - cur;
|
||||
assert ( diff >= 0 );
|
||||
skipOrThrow( diff );
|
||||
}
|
||||
|
||||
public void skipOrThrow( long length ) throws IOException {
|
||||
if( skip( length ) != length ) throw new EOFException();
|
||||
}
|
||||
|
||||
public ByteOrder getByteOrder() {
|
||||
return mByteBuffer.order();
|
||||
}
|
||||
|
||||
public void setByteOrder( ByteOrder order ) {
|
||||
mByteBuffer.order( order );
|
||||
}
|
||||
|
||||
public int readUnsignedShort() throws IOException {
|
||||
return readShort() & 0xffff;
|
||||
}
|
||||
|
||||
public short readShort() throws IOException {
|
||||
readOrThrow( mByteArray, 0, 2 );
|
||||
mByteBuffer.rewind();
|
||||
return mByteBuffer.getShort();
|
||||
}
|
||||
|
||||
public byte readByte() throws IOException {
|
||||
readOrThrow( mByteArray, 0, 1 );
|
||||
mByteBuffer.rewind();
|
||||
return mByteBuffer.get();
|
||||
}
|
||||
|
||||
public int readUnsignedByte() throws IOException {
|
||||
readOrThrow( mByteArray, 0, 1 );
|
||||
mByteBuffer.rewind();
|
||||
return (mByteBuffer.get() & 0xff);
|
||||
}
|
||||
|
||||
public void readOrThrow( byte[] b, int off, int len ) throws IOException {
|
||||
int r = read( b, off, len );
|
||||
if( r != len ) throw new EOFException();
|
||||
}
|
||||
|
||||
public long readUnsignedInt() throws IOException {
|
||||
return readInt() & 0xffffffffL;
|
||||
}
|
||||
|
||||
public int readInt() throws IOException {
|
||||
readOrThrow( mByteArray, 0, 4 );
|
||||
mByteBuffer.rewind();
|
||||
return mByteBuffer.getInt();
|
||||
}
|
||||
|
||||
public long readLong() throws IOException {
|
||||
readOrThrow( mByteArray, 0, 8 );
|
||||
mByteBuffer.rewind();
|
||||
return mByteBuffer.getLong();
|
||||
}
|
||||
|
||||
public String readString( int n ) throws IOException {
|
||||
byte buf[] = new byte[n];
|
||||
readOrThrow( buf );
|
||||
return new String( buf, "UTF8" );
|
||||
}
|
||||
|
||||
public void readOrThrow( byte[] b ) throws IOException {
|
||||
readOrThrow( b, 0, b.length );
|
||||
}
|
||||
|
||||
public String readString( int n, Charset charset ) throws IOException {
|
||||
byte buf[] = new byte[n];
|
||||
readOrThrow( buf );
|
||||
return new String( buf, charset );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import java.io.EOFException
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@Suppress("unused")
|
||||
internal class CountedDataInputStream constructor(`in` : InputStream) :
|
||||
FilterInputStream(`in`) {
|
||||
|
||||
// allocate a byte buffer for a long value;
|
||||
private val mByteArray = ByteArray(8)
|
||||
private val mByteBuffer = ByteBuffer.wrap(mByteArray)
|
||||
var readByteCount = 0
|
||||
private set
|
||||
var end = 0
|
||||
|
||||
var byteOrder : ByteOrder
|
||||
get() = mByteBuffer.order()
|
||||
set(order) {
|
||||
mByteBuffer.order(order)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(b : ByteArray) : Int {
|
||||
val r = `in`.read(b)
|
||||
readByteCount += if(r >= 0) r else 0
|
||||
return r
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read() : Int {
|
||||
val r = `in`.read()
|
||||
readByteCount += if(r >= 0) 1 else 0
|
||||
return r
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(b : ByteArray, off : Int, len : Int) : Int {
|
||||
val r = `in`.read(b, off, len)
|
||||
readByteCount += if(r >= 0) r else 0
|
||||
return r
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun skip(length : Long) : Long {
|
||||
val skip = `in`.skip(length)
|
||||
readByteCount += skip.toInt()
|
||||
return skip
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun skipTo(target : Long) {
|
||||
val cur = readByteCount.toLong()
|
||||
val diff = target - cur
|
||||
if(diff < 0) throw IndexOutOfBoundsException("skipTo: negative move")
|
||||
skipOrThrow(diff)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun skipOrThrow(length : Long) {
|
||||
if(skip(length) != length) throw EOFException()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readUnsignedShort() : Int = readShort().toInt() and 0xffff
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readShort() : Short {
|
||||
readOrThrow(mByteArray, 0, 2)
|
||||
mByteBuffer.rewind()
|
||||
return mByteBuffer.short
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readByte() : Byte {
|
||||
readOrThrow(mByteArray, 0, 1)
|
||||
mByteBuffer.rewind()
|
||||
return mByteBuffer.get()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readUnsignedByte() : Int {
|
||||
readOrThrow(mByteArray, 0, 1)
|
||||
mByteBuffer.rewind()
|
||||
return mByteBuffer.get().toInt() and 0xff
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@JvmOverloads
|
||||
fun readOrThrow(b : ByteArray, off : Int = 0, len : Int = b.size) {
|
||||
val r = read(b, off, len)
|
||||
if(r != len) throw EOFException()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readUnsignedInt() : Long {
|
||||
return readInt().toLong() and 0xffffffffL
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readInt() : Int {
|
||||
readOrThrow(mByteArray, 0, 4)
|
||||
mByteBuffer.rewind()
|
||||
return mByteBuffer.int
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readLong() : Long {
|
||||
readOrThrow(mByteArray, 0, 8)
|
||||
mByteBuffer.rewind()
|
||||
return mByteBuffer.long
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readString(n : Int) : String {
|
||||
val buf = ByteArray(n)
|
||||
readOrThrow(buf)
|
||||
return String(buf, StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readString(n : Int, charset : Charset) : String {
|
||||
val buf = ByteArray(n)
|
||||
readOrThrow(buf)
|
||||
return String(buf, charset)
|
||||
}
|
||||
}
|
|
@ -1,383 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class stores the EXIF header in IFDs according to the JPEG
|
||||
* specification. It is the result produced by {@link ExifReader}.
|
||||
*
|
||||
* @see ExifReader
|
||||
* @see IfdData
|
||||
*/
|
||||
class ExifData {
|
||||
private static final String TAG = "ExifData";
|
||||
private static final byte[] USER_COMMENT_ASCII = { 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 };
|
||||
private static final byte[] USER_COMMENT_JIS = { 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
private static final byte[] USER_COMMENT_UNICODE = { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 };
|
||||
|
||||
private List<ExifParser.Section> mSections;
|
||||
private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
|
||||
private final ByteOrder mByteOrder;
|
||||
private byte[] mThumbnail;
|
||||
private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
|
||||
private int qualityGuess = 0;
|
||||
private int imageLength = -1, imageWidth = -1;
|
||||
private short jpegProcess = 0;
|
||||
public int mUncompressedDataPosition = 0;
|
||||
|
||||
ExifData( ByteOrder order ) {
|
||||
mByteOrder = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compressed thumbnail. Returns null if there is no compressed
|
||||
* thumbnail.
|
||||
*
|
||||
* @see #hasCompressedThumbnail()
|
||||
*/
|
||||
protected byte[] getCompressedThumbnail() {
|
||||
return mThumbnail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compressed thumbnail.
|
||||
*/
|
||||
protected void setCompressedThumbnail( byte[] thumbnail ) {
|
||||
mThumbnail = thumbnail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true it this header contains a compressed thumbnail.
|
||||
*/
|
||||
protected boolean hasCompressedThumbnail() {
|
||||
return mThumbnail != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an uncompressed strip.
|
||||
*/
|
||||
protected void setStripBytes( int index, byte[] strip ) {
|
||||
if( index < mStripBytes.size() ) {
|
||||
mStripBytes.set( index, strip );
|
||||
}
|
||||
else {
|
||||
for( int i = mStripBytes.size(); i < index; i++ ) {
|
||||
mStripBytes.add( null );
|
||||
}
|
||||
mStripBytes.add( strip );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strip count.
|
||||
*/
|
||||
protected int getStripCount() {
|
||||
return mStripBytes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strip at the specified index.
|
||||
*
|
||||
* @exceptions #IndexOutOfBoundException
|
||||
*/
|
||||
protected byte[] getStrip( int index ) {
|
||||
return mStripBytes.get( index );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this header contains uncompressed strip.
|
||||
*/
|
||||
protected boolean hasUncompressedStrip() {
|
||||
return mStripBytes.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the byte order.
|
||||
*/
|
||||
protected ByteOrder getByteOrder() {
|
||||
return mByteOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds IFD data. If IFD data of the same type already exists, it will be
|
||||
* replaced by the new data.
|
||||
*/
|
||||
protected void addIfdData( IfdData data ) {
|
||||
mIfdDatas[data.getId()] = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tag with a given TID in the given IFD if the tag exists.
|
||||
* Otherwise returns null.
|
||||
*/
|
||||
protected ExifTag getTag( short tag, int ifd ) {
|
||||
IfdData ifdData = mIfdDatas[ifd];
|
||||
return ( ifdData == null ) ? null : ifdData.getTag( tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ExifTag to its default IFD and returns an existing ExifTag
|
||||
* with the same TID or null if none exist.
|
||||
*/
|
||||
protected ExifTag addTag( ExifTag tag ) {
|
||||
if( tag != null ) {
|
||||
int ifd = tag.getIfd();
|
||||
return addTag( tag, ifd );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ExifTag to the given IFD and returns an existing ExifTag
|
||||
* with the same TID or null if none exist.
|
||||
*/
|
||||
protected ExifTag addTag( ExifTag tag, int ifdId ) {
|
||||
if( tag != null && ExifTag.isValidIfd( ifdId ) ) {
|
||||
IfdData ifdData = getOrCreateIfdData( ifdId );
|
||||
return ifdData.setTag( tag );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IfdData} object corresponding to a given IFD or
|
||||
* generates one if none exist.
|
||||
*/
|
||||
protected IfdData getOrCreateIfdData( int ifdId ) {
|
||||
IfdData ifdData = mIfdDatas[ifdId];
|
||||
if( ifdData == null ) {
|
||||
ifdData = new IfdData( ifdId );
|
||||
mIfdDatas[ifdId] = ifdData;
|
||||
}
|
||||
return ifdData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the thumbnail and its related tags. IFD1 will be removed.
|
||||
*/
|
||||
protected void removeThumbnailData() {
|
||||
clearThumbnailAndStrips();
|
||||
mIfdDatas[IfdId.TYPE_IFD_1] = null;
|
||||
}
|
||||
|
||||
protected void clearThumbnailAndStrips() {
|
||||
mThumbnail = null;
|
||||
mStripBytes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tag with a given TID and IFD.
|
||||
*/
|
||||
protected void removeTag( short tagId, int ifdId ) {
|
||||
IfdData ifdData = mIfdDatas[ifdId];
|
||||
if( ifdData == null ) {
|
||||
return;
|
||||
}
|
||||
ifdData.removeTag( tagId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the user comment tag into string as specified in the EXIF
|
||||
* standard. Returns null if decoding failed.
|
||||
*/
|
||||
protected String getUserComment() {
|
||||
IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
|
||||
if( ifdData == null ) {
|
||||
return null;
|
||||
}
|
||||
ExifTag tag = ifdData.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_USER_COMMENT ) );
|
||||
if( tag == null ) {
|
||||
return null;
|
||||
}
|
||||
if( tag.getComponentCount() < 8 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] buf = new byte[tag.getComponentCount()];
|
||||
tag.getBytes( buf );
|
||||
|
||||
byte[] code = new byte[8];
|
||||
System.arraycopy( buf, 0, code, 0, 8 );
|
||||
|
||||
try {
|
||||
if( Arrays.equals( code, USER_COMMENT_ASCII ) ) {
|
||||
return new String( buf, 8, buf.length - 8, "US-ASCII" );
|
||||
}
|
||||
else if( Arrays.equals( code, USER_COMMENT_JIS ) ) {
|
||||
return new String( buf, 8, buf.length - 8, "EUC-JP" );
|
||||
}
|
||||
else if( Arrays.equals( code, USER_COMMENT_UNICODE ) ) {
|
||||
return new String( buf, 8, buf.length - 8, "UTF-16" );
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
} catch( UnsupportedEncodingException e ) {
|
||||
Log.w( TAG, "Failed to decode the user comment" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link ExifTag}s in the ExifData or null if there
|
||||
* are none.
|
||||
*/
|
||||
protected List<ExifTag> getAllTags() {
|
||||
ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
|
||||
for( IfdData d : mIfdDatas ) {
|
||||
if( d != null ) {
|
||||
ExifTag[] tags = d.getAllTags();
|
||||
if( tags != null ) {
|
||||
for( ExifTag t : tags ) {
|
||||
ret.add( t );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( ret.size() == 0 ) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link ExifTag}s in a given IFD or null if there
|
||||
* are none.
|
||||
*/
|
||||
protected List<ExifTag> getAllTagsForIfd( int ifd ) {
|
||||
IfdData d = mIfdDatas[ifd];
|
||||
if( d == null ) {
|
||||
return null;
|
||||
}
|
||||
ExifTag[] tags = d.getAllTags();
|
||||
if( tags == null ) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<ExifTag> ret = new ArrayList<ExifTag>( tags.length );
|
||||
for( ExifTag t : tags ) {
|
||||
ret.add( t );
|
||||
}
|
||||
if( ret.size() == 0 ) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link ExifTag}s with a given TID or null if there
|
||||
* are none.
|
||||
*/
|
||||
protected List<ExifTag> getAllTagsForTagId( short tag ) {
|
||||
ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
|
||||
for( IfdData d : mIfdDatas ) {
|
||||
if( d != null ) {
|
||||
ExifTag t = d.getTag( tag );
|
||||
if( t != null ) {
|
||||
ret.add( t );
|
||||
}
|
||||
}
|
||||
}
|
||||
if( ret.size() == 0 ) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object obj ) {
|
||||
if( this == obj ) {
|
||||
return true;
|
||||
}
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
}
|
||||
if( obj instanceof ExifData ) {
|
||||
ExifData data = (ExifData) obj;
|
||||
if( data.mByteOrder != mByteOrder ||
|
||||
data.mStripBytes.size() != mStripBytes.size() ||
|
||||
! Arrays.equals( data.mThumbnail, mThumbnail ) ) {
|
||||
return false;
|
||||
}
|
||||
for( int i = 0; i < mStripBytes.size(); i++ ) {
|
||||
if( ! Arrays.equals( data.mStripBytes.get( i ), mStripBytes.get( i ) ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for( int i = 0; i < IfdId.TYPE_IFD_COUNT; i++ ) {
|
||||
IfdData ifd1 = data.getIfdData( i );
|
||||
IfdData ifd2 = getIfdData( i );
|
||||
if( ifd1 != ifd2 && ifd1 != null && ! ifd1.equals( ifd2 ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IfdData} object corresponding to a given IFD if it
|
||||
* exists or null.
|
||||
*/
|
||||
protected IfdData getIfdData( int ifdId ) {
|
||||
if( ExifTag.isValidIfd( ifdId ) ) {
|
||||
return mIfdDatas[ifdId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void setQualityGuess( final int qualityGuess ) {
|
||||
this.qualityGuess = qualityGuess;
|
||||
}
|
||||
|
||||
public int getQualityGuess() {
|
||||
return qualityGuess;
|
||||
}
|
||||
|
||||
protected void setImageSize( final int imageWidth, final int imageLength ) {
|
||||
this.imageWidth = imageWidth;
|
||||
this.imageLength = imageLength;
|
||||
}
|
||||
|
||||
public int[] getImageSize() {
|
||||
return new int[]{ imageWidth, imageLength };
|
||||
}
|
||||
|
||||
public void setJpegProcess( final short jpegProcess ) {
|
||||
this.jpegProcess = jpegProcess;
|
||||
}
|
||||
|
||||
public short getJpegProcess() {
|
||||
return this.jpegProcess;
|
||||
}
|
||||
|
||||
public void setSections( final List<ExifParser.Section> sections ) {
|
||||
mSections = sections;
|
||||
}
|
||||
|
||||
public List<ExifParser.Section> getSections() {
|
||||
return mSections;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.charset.Charset
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* This class stores the EXIF header in IFDs according to the JPEG
|
||||
* specification. It is the result produced by [ExifReader].
|
||||
*
|
||||
* @see ExifReader
|
||||
*
|
||||
* @see IfdData
|
||||
*/
|
||||
@Suppress("unused")
|
||||
internal open class ExifData(
|
||||
/**
|
||||
* Gets the byte order.
|
||||
*/
|
||||
val byteOrder : ByteOrder
|
||||
) {
|
||||
|
||||
var sections : List<ExifParser.Section>? = null
|
||||
private val mIfdDatas = arrayOfNulls<IfdData>(IfdId.TYPE_IFD_COUNT)
|
||||
/**
|
||||
* Gets the compressed thumbnail. Returns null if there is no compressed
|
||||
* thumbnail.
|
||||
*
|
||||
* @see .hasCompressedThumbnail
|
||||
*/
|
||||
/**
|
||||
* Sets the compressed thumbnail.
|
||||
*/
|
||||
var compressedThumbnail : ByteArray? = null
|
||||
private val mStripBytes = ArrayList<ByteArray?>()
|
||||
var qualityGuess = 0
|
||||
private var imageLength = - 1
|
||||
private var imageWidth = - 1
|
||||
var jpegProcess : Short = 0
|
||||
var mUncompressedDataPosition = 0
|
||||
|
||||
/**
|
||||
* Gets the strip count.
|
||||
*/
|
||||
val stripCount : Int
|
||||
get() = mStripBytes.size
|
||||
|
||||
/**
|
||||
* Decodes the user comment tag into string as specified in the EXIF
|
||||
* standard. Returns null if decoding failed.
|
||||
*/
|
||||
val userComment : String?
|
||||
get() {
|
||||
val ifdData = mIfdDatas[IfdId.TYPE_IFD_0] ?: return null
|
||||
val tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT))
|
||||
?: return null
|
||||
if(tag.componentCount < 8) {
|
||||
return null
|
||||
}
|
||||
|
||||
val buf = ByteArray(tag.componentCount)
|
||||
tag.getBytes(buf)
|
||||
|
||||
val code = ByteArray(8)
|
||||
System.arraycopy(buf, 0, code, 0, 8)
|
||||
|
||||
return try {
|
||||
when {
|
||||
code.contentEquals(USER_COMMENT_ASCII) -> String(
|
||||
buf,
|
||||
8,
|
||||
buf.size - 8,
|
||||
Charsets.US_ASCII
|
||||
)
|
||||
code.contentEquals(USER_COMMENT_JIS) -> String(
|
||||
buf,
|
||||
8,
|
||||
buf.size - 8,
|
||||
Charset.forName("EUC-JP")
|
||||
)
|
||||
code.contentEquals(USER_COMMENT_UNICODE) -> String(
|
||||
buf,
|
||||
8,
|
||||
buf.size - 8,
|
||||
Charsets.UTF_16
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
} catch(e : UnsupportedEncodingException) {
|
||||
Log.w(TAG, "Failed to decode the user comment")
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all [ExifTag]s in the ExifData or null if there
|
||||
* are none.
|
||||
*/
|
||||
val allTags : List<ExifTag>?
|
||||
get() {
|
||||
val ret = ArrayList<ExifTag>()
|
||||
mIfdDatas.forEach { it?.allTags?.forEach { tag->ret.add(tag) } }
|
||||
return if(ret.isEmpty()) null else ret
|
||||
}
|
||||
|
||||
val imageSize : IntArray
|
||||
get() = intArrayOf(imageWidth, imageLength)
|
||||
|
||||
/**
|
||||
* Returns true it this header contains a compressed thumbnail.
|
||||
*/
|
||||
fun hasCompressedThumbnail() : Boolean {
|
||||
return compressedThumbnail != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an uncompressed strip.
|
||||
*/
|
||||
fun setStripBytes(index : Int, strip : ByteArray) {
|
||||
if(index < mStripBytes.size) {
|
||||
mStripBytes[index] = strip
|
||||
} else {
|
||||
for(i in mStripBytes.size until index) {
|
||||
mStripBytes.add(null)
|
||||
}
|
||||
mStripBytes.add(strip)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strip at the specified index.
|
||||
*
|
||||
* @exceptions #IndexOutOfBoundException
|
||||
*/
|
||||
fun getStrip(index : Int) : ByteArray? = mStripBytes[index]
|
||||
|
||||
/**
|
||||
* Returns true if this header contains uncompressed strip.
|
||||
*/
|
||||
fun hasUncompressedStrip() : Boolean = mStripBytes.isNotEmpty()
|
||||
|
||||
/**
|
||||
* Adds IFD data. If IFD data of the same type already exists, it will be
|
||||
* replaced by the new data.
|
||||
*/
|
||||
fun addIfdData(data : IfdData) {
|
||||
mIfdDatas[data.id] = data
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tag with a given TID in the given IFD if the tag exists.
|
||||
* Otherwise returns null.
|
||||
*/
|
||||
fun getTag(tag : Short, ifd : Int) : ExifTag? {
|
||||
val ifdData = mIfdDatas[ifd]
|
||||
return ifdData?.getTag(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ExifTag to its default IFD and returns an existing ExifTag
|
||||
* with the same TID or null if none exist.
|
||||
*/
|
||||
fun addTag(tag : ExifTag?) : ExifTag? {
|
||||
if(tag != null) {
|
||||
val ifd = tag.ifd
|
||||
return addTag(tag, ifd)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ExifTag to the given IFD and returns an existing ExifTag
|
||||
* with the same TID or null if none exist.
|
||||
*/
|
||||
private fun addTag(tag : ExifTag?, ifdId : Int) : ExifTag? {
|
||||
if(tag != null && ExifTag.isValidIfd(ifdId)) {
|
||||
val ifdData = getOrCreateIfdData(ifdId)
|
||||
return ifdData.setTag(tag)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [IfdData] object corresponding to a given IFD or
|
||||
* generates one if none exist.
|
||||
*/
|
||||
private fun getOrCreateIfdData(ifdId : Int) : IfdData {
|
||||
var ifdData : IfdData? = mIfdDatas[ifdId]
|
||||
if(ifdData == null) {
|
||||
ifdData = IfdData(ifdId)
|
||||
mIfdDatas[ifdId] = ifdData
|
||||
}
|
||||
return ifdData
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the thumbnail and its related tags. IFD1 will be removed.
|
||||
*/
|
||||
protected fun removeThumbnailData() {
|
||||
clearThumbnailAndStrips()
|
||||
mIfdDatas[IfdId.TYPE_IFD_1] = null
|
||||
}
|
||||
|
||||
fun clearThumbnailAndStrips() {
|
||||
compressedThumbnail = null
|
||||
mStripBytes.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tag with a given TID and IFD.
|
||||
*/
|
||||
fun removeTag(tagId : Short, ifdId : Int) {
|
||||
val ifdData = mIfdDatas[ifdId] ?: return
|
||||
ifdData.removeTag(tagId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all [ExifTag]s in a given IFD or null if there
|
||||
* are none.
|
||||
*/
|
||||
fun getAllTagsForIfd(ifd : Int) : List<ExifTag>? {
|
||||
val d = mIfdDatas[ifd] ?: return null
|
||||
val tags = d.allTags ?: return null
|
||||
val ret = ArrayList<ExifTag>(tags.size)
|
||||
for(t in tags) {
|
||||
ret.add(t)
|
||||
}
|
||||
return if(ret.size == 0) {
|
||||
null
|
||||
} else ret
|
||||
}
|
||||
|
||||
// Returns a list of all [ExifTag]s with a given TID
|
||||
// or null if there are none.
|
||||
fun getAllTagsForTagId(tag : Short) : List<ExifTag>? {
|
||||
val ret = ArrayList<ExifTag>()
|
||||
for(d in mIfdDatas) {
|
||||
if(d != null) {
|
||||
val t = d.getTag(tag)
|
||||
if(t != null) {
|
||||
ret.add(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if(ret.isEmpty()) null else ret
|
||||
}
|
||||
|
||||
override fun equals(other : Any?) : Boolean {
|
||||
if(this === other) return true
|
||||
if(other is ExifData) {
|
||||
if(other.byteOrder != byteOrder
|
||||
|| other.mStripBytes.size != mStripBytes.size
|
||||
|| ! Arrays.equals(other.compressedThumbnail, compressedThumbnail)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
for(i in mStripBytes.indices) {
|
||||
val a = mStripBytes[i]
|
||||
val b = other.mStripBytes[i]
|
||||
|
||||
if(a != null && b != null) {
|
||||
if(! a.contentEquals(b)) return false // 内容が異なる
|
||||
} else if((a == null) xor (b == null)) {
|
||||
return false // 片方だけnull
|
||||
}
|
||||
}
|
||||
|
||||
for(i in 0 until IfdId.TYPE_IFD_COUNT) {
|
||||
val ifd1 = other.getIfdData(i)
|
||||
val ifd2 = getIfdData(i)
|
||||
if(ifd1 != ifd2) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [IfdData] object corresponding to a given IFD if it
|
||||
* exists or null.
|
||||
*/
|
||||
fun getIfdData(ifdId : Int) : IfdData? {
|
||||
return if(ExifTag.isValidIfd(ifdId)) {
|
||||
mIfdDatas[ifdId]
|
||||
} else null
|
||||
}
|
||||
|
||||
fun setImageSize(imageWidth : Int, imageLength : Int) {
|
||||
this.imageWidth = imageWidth
|
||||
this.imageLength = imageLength
|
||||
}
|
||||
|
||||
override fun hashCode() : Int {
|
||||
var result = byteOrder.hashCode()
|
||||
result = 31 * result + (sections?.hashCode() ?: 0)
|
||||
result = 31 * result + mIfdDatas.contentHashCode()
|
||||
result = 31 * result + (compressedThumbnail?.contentHashCode() ?: 0)
|
||||
result = 31 * result + mStripBytes.hashCode()
|
||||
result = 31 * result + qualityGuess
|
||||
result = 31 * result + imageLength
|
||||
result = 31 * result + imageWidth
|
||||
result = 31 * result + jpegProcess
|
||||
result = 31 * result + mUncompressedDataPosition
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExifData"
|
||||
private val USER_COMMENT_ASCII = byteArrayOf(0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00)
|
||||
private val USER_COMMENT_JIS = byteArrayOf(0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00)
|
||||
private val USER_COMMENT_UNICODE =
|
||||
byteArrayOf(0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -14,10 +14,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
public class ExifInvalidFormatException extends Exception {
|
||||
public ExifInvalidFormatException( String meg ) {
|
||||
super( meg );
|
||||
}
|
||||
}
|
||||
class ExifInvalidFormatException(meg : String) : Exception(meg)
|
|
@ -1,379 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class ExifOutputStream {
|
||||
private static final String TAG = "ExifOutputStream";
|
||||
private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
|
||||
|
||||
private static final int STATE_SOI = 0;
|
||||
private static final int EXIF_HEADER = 0x45786966;
|
||||
private static final short TIFF_HEADER = 0x002A;
|
||||
private static final short TIFF_BIG_ENDIAN = 0x4d4d;
|
||||
private static final short TIFF_LITTLE_ENDIAN = 0x4949;
|
||||
private static final short TAG_SIZE = 12;
|
||||
private static final short TIFF_HEADER_SIZE = 8;
|
||||
private static final int MAX_EXIF_SIZE = 65535;
|
||||
private final ExifInterface mInterface;
|
||||
private ExifData mExifData;
|
||||
private ByteBuffer mBuffer = ByteBuffer.allocate( 4 );
|
||||
|
||||
protected ExifOutputStream( ExifInterface iRef ) {
|
||||
mInterface = iRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Exif header to be written into the JPEF file.
|
||||
*/
|
||||
protected ExifData getExifData() {
|
||||
return mExifData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ExifData to be written into the JPEG file. Should be called
|
||||
* before writing image data.
|
||||
*/
|
||||
protected void setExifData( ExifData exifData ) {
|
||||
mExifData = exifData;
|
||||
}
|
||||
|
||||
private int requestByteToBuffer(
|
||||
int requestByteCount, byte[] buffer, int offset, int length ) {
|
||||
int byteNeeded = requestByteCount - mBuffer.position();
|
||||
int byteToRead = length > byteNeeded ? byteNeeded : length;
|
||||
mBuffer.put( buffer, offset, byteToRead );
|
||||
return byteToRead;
|
||||
}
|
||||
|
||||
public void writeExifData( OutputStream out ) throws IOException {
|
||||
if( mExifData == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.v( TAG, "Writing exif data..." );
|
||||
|
||||
ArrayList<ExifTag> nullTags = stripNullValueTags( mExifData );
|
||||
createRequiredIfdAndTag();
|
||||
int exifSize = calculateAllOffset();
|
||||
// Log.i(TAG, "exifSize: " + (exifSize + 8));
|
||||
if( exifSize + 8 > MAX_EXIF_SIZE ) {
|
||||
throw new IOException( "Exif header is too large (>64Kb)" );
|
||||
}
|
||||
|
||||
BufferedOutputStream outputStream = new BufferedOutputStream( out, STREAMBUFFER_SIZE );
|
||||
OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream( outputStream );
|
||||
|
||||
dataOutputStream.setByteOrder( ByteOrder.BIG_ENDIAN );
|
||||
|
||||
dataOutputStream.write( 0xFF );
|
||||
dataOutputStream.write( JpegHeader.TAG_M_EXIF );
|
||||
dataOutputStream.writeShort( (short) ( exifSize + 8 ) );
|
||||
dataOutputStream.writeInt( EXIF_HEADER );
|
||||
dataOutputStream.writeShort( (short) 0x0000 );
|
||||
if( mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN ) {
|
||||
dataOutputStream.writeShort( TIFF_BIG_ENDIAN );
|
||||
}
|
||||
else {
|
||||
dataOutputStream.writeShort( TIFF_LITTLE_ENDIAN );
|
||||
}
|
||||
dataOutputStream.setByteOrder( mExifData.getByteOrder() );
|
||||
dataOutputStream.writeShort( TIFF_HEADER );
|
||||
dataOutputStream.writeInt( 8 );
|
||||
writeAllTags( dataOutputStream );
|
||||
|
||||
writeThumbnail( dataOutputStream );
|
||||
|
||||
for( ExifTag t : nullTags ) {
|
||||
mExifData.addTag( t );
|
||||
}
|
||||
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
|
||||
private ArrayList<ExifTag> stripNullValueTags( ExifData data ) {
|
||||
ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
|
||||
for( ExifTag t : data.getAllTags() ) {
|
||||
if( t.getValue() == null && ! ExifInterface.isOffsetTag( t.getTagId() ) ) {
|
||||
data.removeTag( t.getTagId(), t.getIfd() );
|
||||
nullTags.add( t );
|
||||
}
|
||||
}
|
||||
return nullTags;
|
||||
}
|
||||
|
||||
private void writeThumbnail( OrderedDataOutputStream dataOutputStream ) throws IOException {
|
||||
if( mExifData.hasCompressedThumbnail() ) {
|
||||
Log.d( TAG, "writing thumbnail.." );
|
||||
dataOutputStream.write( mExifData.getCompressedThumbnail() );
|
||||
}
|
||||
else if( mExifData.hasUncompressedStrip() ) {
|
||||
Log.d( TAG, "writing uncompressed strip.." );
|
||||
for( int i = 0; i < mExifData.getStripCount(); i++ ) {
|
||||
dataOutputStream.write( mExifData.getStrip( i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAllTags( OrderedDataOutputStream dataOutputStream ) throws IOException {
|
||||
writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_0 ), dataOutputStream );
|
||||
writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_EXIF ), dataOutputStream );
|
||||
IfdData interoperabilityIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
|
||||
if( interoperabilityIfd != null ) {
|
||||
writeIfd( interoperabilityIfd, dataOutputStream );
|
||||
}
|
||||
IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
|
||||
if( gpsIfd != null ) {
|
||||
writeIfd( gpsIfd, dataOutputStream );
|
||||
}
|
||||
IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
|
||||
if( ifd1 != null ) {
|
||||
writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_1 ), dataOutputStream );
|
||||
}
|
||||
}
|
||||
|
||||
private void writeIfd( IfdData ifd, OrderedDataOutputStream dataOutputStream ) throws IOException {
|
||||
ExifTag[] tags = ifd.getAllTags();
|
||||
dataOutputStream.writeShort( (short) tags.length );
|
||||
for( ExifTag tag : tags ) {
|
||||
dataOutputStream.writeShort( tag.getTagId() );
|
||||
dataOutputStream.writeShort( tag.getDataType() );
|
||||
dataOutputStream.writeInt( tag.getComponentCount() );
|
||||
// Log.v( TAG, "\n" + tag.toString() );
|
||||
if( tag.getDataSize() > 4 ) {
|
||||
dataOutputStream.writeInt( tag.getOffset() );
|
||||
}
|
||||
else {
|
||||
ExifOutputStream.writeTagValue( tag, dataOutputStream );
|
||||
for( int i = 0, n = 4 - tag.getDataSize(); i < n; i++ ) {
|
||||
dataOutputStream.write( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
dataOutputStream.writeInt( ifd.getOffsetToNextIfd() );
|
||||
for( ExifTag tag : tags ) {
|
||||
if( tag.getDataSize() > 4 ) {
|
||||
ExifOutputStream.writeTagValue( tag, dataOutputStream );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateOffsetOfIfd( IfdData ifd, int offset ) {
|
||||
offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
|
||||
ExifTag[] tags = ifd.getAllTags();
|
||||
for( ExifTag tag : tags ) {
|
||||
if( tag.getDataSize() > 4 ) {
|
||||
tag.setOffset( offset );
|
||||
offset += tag.getDataSize();
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private void createRequiredIfdAndTag() throws IOException {
|
||||
// IFD0 is required for all file
|
||||
IfdData ifd0 = mExifData.getIfdData( IfdId.TYPE_IFD_0 );
|
||||
if( ifd0 == null ) {
|
||||
ifd0 = new IfdData( IfdId.TYPE_IFD_0 );
|
||||
mExifData.addIfdData( ifd0 );
|
||||
}
|
||||
ExifTag exifOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_EXIF_IFD );
|
||||
if( exifOffsetTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD );
|
||||
}
|
||||
ifd0.setTag( exifOffsetTag );
|
||||
|
||||
// Exif IFD is required for all files.
|
||||
IfdData exifIfd = mExifData.getIfdData( IfdId.TYPE_IFD_EXIF );
|
||||
if( exifIfd == null ) {
|
||||
exifIfd = new IfdData( IfdId.TYPE_IFD_EXIF );
|
||||
mExifData.addIfdData( exifIfd );
|
||||
}
|
||||
|
||||
// GPS IFD
|
||||
IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
|
||||
if( gpsIfd != null ) {
|
||||
ExifTag gpsOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_GPS_IFD );
|
||||
if( gpsOffsetTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_GPS_IFD );
|
||||
}
|
||||
ifd0.setTag( gpsOffsetTag );
|
||||
}
|
||||
|
||||
// Interoperability IFD
|
||||
IfdData interIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
|
||||
if( interIfd != null ) {
|
||||
ExifTag interOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_INTEROPERABILITY_IFD );
|
||||
if( interOffsetTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_INTEROPERABILITY_IFD );
|
||||
}
|
||||
exifIfd.setTag( interOffsetTag );
|
||||
}
|
||||
|
||||
IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
|
||||
|
||||
// thumbnail
|
||||
if( mExifData.hasCompressedThumbnail() ) {
|
||||
|
||||
if( ifd1 == null ) {
|
||||
ifd1 = new IfdData( IfdId.TYPE_IFD_1 );
|
||||
mExifData.addIfdData( ifd1 );
|
||||
}
|
||||
|
||||
ExifTag offsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
|
||||
if( offsetTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
|
||||
}
|
||||
|
||||
ifd1.setTag( offsetTag );
|
||||
ExifTag lengthTag = mInterface.buildUninitializedTag( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
|
||||
if( lengthTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
|
||||
}
|
||||
|
||||
lengthTag.setValue( mExifData.getCompressedThumbnail().length );
|
||||
ifd1.setTag( lengthTag );
|
||||
|
||||
// Get rid of tags for uncompressed if they exist.
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) );
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS ) );
|
||||
}
|
||||
else if( mExifData.hasUncompressedStrip() ) {
|
||||
if( ifd1 == null ) {
|
||||
ifd1 = new IfdData( IfdId.TYPE_IFD_1 );
|
||||
mExifData.addIfdData( ifd1 );
|
||||
}
|
||||
int stripCount = mExifData.getStripCount();
|
||||
ExifTag offsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_STRIP_OFFSETS );
|
||||
if( offsetTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_STRIP_OFFSETS );
|
||||
}
|
||||
ExifTag lengthTag = mInterface.buildUninitializedTag( ExifInterface.TAG_STRIP_BYTE_COUNTS );
|
||||
if( lengthTag == null ) {
|
||||
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_STRIP_BYTE_COUNTS );
|
||||
}
|
||||
long[] lengths = new long[stripCount];
|
||||
for( int i = 0; i < mExifData.getStripCount(); i++ ) {
|
||||
lengths[i] = mExifData.getStrip( i ).length;
|
||||
}
|
||||
lengthTag.setValue( lengths );
|
||||
ifd1.setTag( offsetTag );
|
||||
ifd1.setTag( lengthTag );
|
||||
// Get rid of tags for compressed if they exist.
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) );
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
|
||||
}
|
||||
else if( ifd1 != null ) {
|
||||
// Get rid of offset and length tags if there is no thumbnail.
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) );
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS ) );
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) );
|
||||
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateAllOffset() {
|
||||
int offset = TIFF_HEADER_SIZE;
|
||||
IfdData ifd0 = mExifData.getIfdData( IfdId.TYPE_IFD_0 );
|
||||
offset = calculateOffsetOfIfd( ifd0, offset );
|
||||
ifd0.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_EXIF_IFD ) ).setValue( offset );
|
||||
|
||||
IfdData exifIfd = mExifData.getIfdData( IfdId.TYPE_IFD_EXIF );
|
||||
offset = calculateOffsetOfIfd( exifIfd, offset );
|
||||
|
||||
IfdData interIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
|
||||
if( interIfd != null ) {
|
||||
exifIfd.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_INTEROPERABILITY_IFD ) ).setValue( offset );
|
||||
offset = calculateOffsetOfIfd( interIfd, offset );
|
||||
}
|
||||
|
||||
IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
|
||||
if( gpsIfd != null ) {
|
||||
ifd0.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_GPS_IFD ) ).setValue( offset );
|
||||
offset = calculateOffsetOfIfd( gpsIfd, offset );
|
||||
}
|
||||
|
||||
IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
|
||||
if( ifd1 != null ) {
|
||||
ifd0.setOffsetToNextIfd( offset );
|
||||
offset = calculateOffsetOfIfd( ifd1, offset );
|
||||
}
|
||||
|
||||
// thumbnail
|
||||
if( mExifData.hasCompressedThumbnail() ) {
|
||||
ifd1.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) ).setValue( offset );
|
||||
offset += mExifData.getCompressedThumbnail().length;
|
||||
}
|
||||
else if( mExifData.hasUncompressedStrip() ) {
|
||||
int stripCount = mExifData.getStripCount();
|
||||
long[] offsets = new long[stripCount];
|
||||
for( int i = 0; i < mExifData.getStripCount(); i++ ) {
|
||||
offsets[i] = offset;
|
||||
offset += mExifData.getStrip( i ).length;
|
||||
}
|
||||
ifd1.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) ).setValue( offsets );
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static void writeTagValue( ExifTag tag, OrderedDataOutputStream dataOutputStream ) throws IOException {
|
||||
switch( tag.getDataType() ) {
|
||||
case ExifTag.TYPE_ASCII:
|
||||
byte buf[] = tag.getStringByte();
|
||||
if( buf.length == tag.getComponentCount() ) {
|
||||
buf[buf.length - 1] = 0;
|
||||
dataOutputStream.write( buf );
|
||||
}
|
||||
else {
|
||||
dataOutputStream.write( buf );
|
||||
dataOutputStream.write( 0 );
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_LONG:
|
||||
case ExifTag.TYPE_UNSIGNED_LONG:
|
||||
for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
|
||||
dataOutputStream.writeInt( (int) tag.getValueAt( i ) );
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_RATIONAL:
|
||||
case ExifTag.TYPE_UNSIGNED_RATIONAL:
|
||||
for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
|
||||
dataOutputStream.writeRational( tag.getRational( i ) );
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_UNDEFINED:
|
||||
case ExifTag.TYPE_UNSIGNED_BYTE:
|
||||
buf = new byte[tag.getComponentCount()];
|
||||
tag.getBytes( buf );
|
||||
dataOutputStream.write( buf );
|
||||
break;
|
||||
case ExifTag.TYPE_UNSIGNED_SHORT:
|
||||
for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
|
||||
dataOutputStream.writeShort( (short) tag.getValueAt( i ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.ArrayList
|
||||
|
||||
@Suppress("unused")
|
||||
internal class ExifOutputStream(private val mInterface : ExifInterface) {
|
||||
/**
|
||||
* Gets the Exif header to be written into the JPEF file.
|
||||
*/
|
||||
/**
|
||||
* Sets the ExifData to be written into the JPEG file. Should be called
|
||||
* before writing image data.
|
||||
*/
|
||||
var exifData : ExifData? = null
|
||||
|
||||
private val mBuffer = ByteBuffer.allocate(4)
|
||||
|
||||
private fun requestByteToBuffer(
|
||||
requestByteCount : Int, buffer : ByteArray, offset : Int, length : Int
|
||||
) : Int {
|
||||
val byteNeeded = requestByteCount - mBuffer.position()
|
||||
val byteToRead = if(length > byteNeeded) byteNeeded else length
|
||||
mBuffer.put(buffer, offset, byteToRead)
|
||||
return byteToRead
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeExifData(out : OutputStream) {
|
||||
if(exifData == null) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.v(TAG, "Writing exif data...")
|
||||
|
||||
val nullTags = stripNullValueTags(exifData !!)
|
||||
createRequiredIfdAndTag()
|
||||
val exifSize = calculateAllOffset()
|
||||
// Log.i(TAG, "exifSize: " + (exifSize + 8));
|
||||
if(exifSize + 8 > MAX_EXIF_SIZE) {
|
||||
throw IOException("Exif header is too large (>64Kb)")
|
||||
}
|
||||
|
||||
val outputStream = BufferedOutputStream(out, STREAMBUFFER_SIZE)
|
||||
val dataOutputStream = OrderedDataOutputStream(outputStream)
|
||||
|
||||
dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN)
|
||||
|
||||
dataOutputStream.write(0xFF)
|
||||
dataOutputStream.write(JpegHeader.TAG_M_EXIF)
|
||||
dataOutputStream.writeShort((exifSize + 8).toShort())
|
||||
dataOutputStream.writeInt(EXIF_HEADER)
|
||||
dataOutputStream.writeShort(0x0000.toShort())
|
||||
if(exifData !!.byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||
dataOutputStream.writeShort(TIFF_BIG_ENDIAN)
|
||||
} else {
|
||||
dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN)
|
||||
}
|
||||
dataOutputStream.setByteOrder(exifData !!.byteOrder)
|
||||
dataOutputStream.writeShort(TIFF_HEADER)
|
||||
dataOutputStream.writeInt(8)
|
||||
writeAllTags(dataOutputStream)
|
||||
|
||||
writeThumbnail(dataOutputStream)
|
||||
|
||||
for(t in nullTags) {
|
||||
exifData !!.addTag(t)
|
||||
}
|
||||
|
||||
dataOutputStream.flush()
|
||||
}
|
||||
|
||||
private fun stripNullValueTags(data : ExifData) : ArrayList<ExifTag> {
|
||||
val nullTags = ArrayList<ExifTag>()
|
||||
for(t in data.allTags !!) {
|
||||
if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) {
|
||||
data.removeTag(t.tagId, t.ifd)
|
||||
nullTags.add(t)
|
||||
}
|
||||
}
|
||||
return nullTags
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) {
|
||||
if(exifData !!.hasCompressedThumbnail()) {
|
||||
Log.d(TAG, "writing thumbnail..")
|
||||
dataOutputStream.write(exifData !!.compressedThumbnail !!)
|
||||
} else if(exifData !!.hasUncompressedStrip()) {
|
||||
Log.d(TAG, "writing uncompressed strip..")
|
||||
for(i in 0 until exifData !!.stripCount) {
|
||||
dataOutputStream.write(exifData !!.getStrip(i) !!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeAllTags(dataOutputStream : OrderedDataOutputStream) {
|
||||
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_0) !!, dataOutputStream)
|
||||
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF) !!, dataOutputStream)
|
||||
val interoperabilityIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
|
||||
if(interoperabilityIfd != null) {
|
||||
writeIfd(interoperabilityIfd, dataOutputStream)
|
||||
}
|
||||
val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
|
||||
if(gpsIfd != null) {
|
||||
writeIfd(gpsIfd, dataOutputStream)
|
||||
}
|
||||
val ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
|
||||
if(ifd1 != null) {
|
||||
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_1) !!, dataOutputStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeIfd(ifd : IfdData, dataOutputStream : OrderedDataOutputStream) {
|
||||
val tags = ifd.allTags
|
||||
dataOutputStream.writeShort(tags.size.toShort())
|
||||
for(tag in tags) {
|
||||
dataOutputStream.writeShort(tag.tagId)
|
||||
dataOutputStream.writeShort(tag.dataType)
|
||||
dataOutputStream.writeInt(tag.componentCount)
|
||||
// Log.v( TAG, "\n" + tag.toString() );
|
||||
if(tag.dataSize > 4) {
|
||||
dataOutputStream.writeInt(tag.offset)
|
||||
} else {
|
||||
writeTagValue(tag, dataOutputStream)
|
||||
var i = 0
|
||||
val n = 4 - tag.dataSize
|
||||
while(i < n) {
|
||||
dataOutputStream.write(0)
|
||||
i ++
|
||||
}
|
||||
}
|
||||
}
|
||||
dataOutputStream.writeInt(ifd.offsetToNextIfd)
|
||||
for(tag in tags) {
|
||||
if(tag.dataSize > 4) {
|
||||
writeTagValue(tag, dataOutputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateOffsetOfIfd(ifd : IfdData, offsetArg : Int) : Int {
|
||||
var offset = offsetArg
|
||||
offset += 2 + ifd.tagCount * TAG_SIZE + 4
|
||||
val tags = ifd.allTags
|
||||
for(tag in tags) {
|
||||
if(tag.dataSize > 4) {
|
||||
tag.offset = offset
|
||||
offset += tag.dataSize
|
||||
}
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun createRequiredIfdAndTag() {
|
||||
// IFD0 is required for all file
|
||||
var ifd0 = exifData !!.getIfdData(IfdId.TYPE_IFD_0)
|
||||
if(ifd0 == null) {
|
||||
ifd0 = IfdData(IfdId.TYPE_IFD_0)
|
||||
exifData !!.addIfdData(ifd0)
|
||||
}
|
||||
val exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD)
|
||||
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD)
|
||||
ifd0.setTag(exifOffsetTag)
|
||||
|
||||
// Exif IFD is required for all files.
|
||||
var exifIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF)
|
||||
if(exifIfd == null) {
|
||||
exifIfd = IfdData(IfdId.TYPE_IFD_EXIF)
|
||||
exifData !!.addIfdData(exifIfd)
|
||||
}
|
||||
|
||||
// GPS IFD
|
||||
val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
|
||||
if(gpsIfd != null) {
|
||||
val gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD)
|
||||
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_GPS_IFD)
|
||||
ifd0.setTag(gpsOffsetTag)
|
||||
}
|
||||
|
||||
// Interoperability IFD
|
||||
val interIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
|
||||
if(interIfd != null) {
|
||||
val interOffsetTag =
|
||||
mInterface.buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD)
|
||||
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_INTEROPERABILITY_IFD)
|
||||
exifIfd.setTag(interOffsetTag)
|
||||
}
|
||||
|
||||
var ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
|
||||
|
||||
// thumbnail
|
||||
when {
|
||||
exifData !!.hasCompressedThumbnail() -> {
|
||||
|
||||
if(ifd1 == null) {
|
||||
ifd1 = IfdData(IfdId.TYPE_IFD_1)
|
||||
exifData !!.addIfdData(ifd1)
|
||||
}
|
||||
|
||||
val offsetTag =
|
||||
mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)
|
||||
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT}")
|
||||
|
||||
ifd1.setTag(offsetTag)
|
||||
val lengthTag =
|
||||
mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
|
||||
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH}")
|
||||
|
||||
lengthTag.setValue(exifData !!.compressedThumbnail !!.size)
|
||||
ifd1.setTag(lengthTag)
|
||||
|
||||
// Get rid of tags for uncompressed if they exist.
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS))
|
||||
}
|
||||
exifData !!.hasUncompressedStrip() -> {
|
||||
if(ifd1 == null) {
|
||||
ifd1 = IfdData(IfdId.TYPE_IFD_1)
|
||||
exifData !!.addIfdData(ifd1)
|
||||
}
|
||||
val stripCount = exifData !!.stripCount
|
||||
val offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS)
|
||||
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_OFFSETS}")
|
||||
val lengthTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS)
|
||||
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_BYTE_COUNTS}")
|
||||
val lengths = LongArray(stripCount)
|
||||
for(i in 0 until exifData !!.stripCount) {
|
||||
lengths[i] = exifData !!.getStrip(i) !!.size.toLong()
|
||||
}
|
||||
lengthTag.setValue(lengths)
|
||||
ifd1.setTag(offsetTag)
|
||||
ifd1.setTag(lengthTag)
|
||||
// Get rid of tags for compressed if they exist.
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
|
||||
}
|
||||
ifd1 != null -> {
|
||||
// Get rid of offset and length tags if there is no thumbnail.
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS))
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateAllOffset() : Int {
|
||||
var offset = TIFF_HEADER_SIZE.toInt()
|
||||
val exifData = this.exifData !!
|
||||
val ifd0 = exifData.getIfdData(IfdId.TYPE_IFD_0)
|
||||
offset = calculateOffsetOfIfd(ifd0 !!, offset)
|
||||
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
|
||||
?.setValue(offset)
|
||||
|
||||
val exifIfd = exifData.getIfdData(IfdId.TYPE_IFD_EXIF)
|
||||
offset = calculateOffsetOfIfd(exifIfd !!, offset)
|
||||
|
||||
val interIfd = exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
|
||||
if(interIfd != null) {
|
||||
exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
|
||||
?.setValue(offset)
|
||||
offset = calculateOffsetOfIfd(interIfd, offset)
|
||||
}
|
||||
|
||||
val gpsIfd = exifData.getIfdData(IfdId.TYPE_IFD_GPS)
|
||||
if(gpsIfd != null) {
|
||||
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
|
||||
?.setValue(offset)
|
||||
offset = calculateOffsetOfIfd(gpsIfd, offset)
|
||||
}
|
||||
|
||||
val ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1)
|
||||
if(ifd1 != null) {
|
||||
ifd0.offsetToNextIfd = offset
|
||||
offset = calculateOffsetOfIfd(ifd1, offset)
|
||||
}
|
||||
|
||||
// thumbnail
|
||||
if(exifData .hasCompressedThumbnail()) {
|
||||
ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
|
||||
?.setValue(offset)
|
||||
offset += exifData .compressedThumbnail !!.size
|
||||
} else if(exifData .hasUncompressedStrip()) {
|
||||
val stripCount = exifData .stripCount
|
||||
val offsets = LongArray(stripCount)
|
||||
for(i in 0 until exifData .stripCount) {
|
||||
offsets[i] = offset.toLong()
|
||||
offset += exifData .getStrip(i) !!.size
|
||||
}
|
||||
ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
|
||||
?.setValue(offsets)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExifOutputStream"
|
||||
private const val STREAMBUFFER_SIZE = 0x00010000 // 64Kb
|
||||
|
||||
private const val STATE_SOI = 0
|
||||
private const val EXIF_HEADER = 0x45786966
|
||||
private const val TIFF_HEADER : Short = 0x002A
|
||||
private const val TIFF_BIG_ENDIAN : Short = 0x4d4d
|
||||
private const val TIFF_LITTLE_ENDIAN : Short = 0x4949
|
||||
private const val TAG_SIZE : Short = 12
|
||||
private const val TIFF_HEADER_SIZE : Short = 8
|
||||
private const val MAX_EXIF_SIZE = 65535
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeTagValue(tag : ExifTag, dataOutputStream : OrderedDataOutputStream) {
|
||||
when(tag.dataType) {
|
||||
ExifTag.TYPE_ASCII -> {
|
||||
val buf = tag.stringByte !!
|
||||
if(buf.size == tag.componentCount) {
|
||||
buf[buf.size - 1] = 0
|
||||
dataOutputStream.write(buf)
|
||||
} else {
|
||||
dataOutputStream.write(buf)
|
||||
dataOutputStream.write(0)
|
||||
}
|
||||
}
|
||||
|
||||
ExifTag.TYPE_LONG, ExifTag.TYPE_UNSIGNED_LONG -> run {
|
||||
for(i in 0 until tag.componentCount) {
|
||||
dataOutputStream.writeInt(tag.getValueAt(i).toInt())
|
||||
}
|
||||
}
|
||||
|
||||
ExifTag.TYPE_RATIONAL, ExifTag.TYPE_UNSIGNED_RATIONAL -> run {
|
||||
for(i in 0 until tag.componentCount) {
|
||||
dataOutputStream.writeRational(tag.getRational(i) !!)
|
||||
}
|
||||
}
|
||||
|
||||
ExifTag.TYPE_UNDEFINED, ExifTag.TYPE_UNSIGNED_BYTE -> {
|
||||
val buf = ByteArray(tag.componentCount)
|
||||
tag.getBytes(buf)
|
||||
dataOutputStream.write(buf)
|
||||
}
|
||||
|
||||
ExifTag.TYPE_UNSIGNED_SHORT -> {
|
||||
for(i in 0 until tag.componentCount) {
|
||||
dataOutputStream.writeShort(tag.getValueAt(i).toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This class reads the EXIF header of a JPEG file and stores it in
|
||||
* {@link ExifData}.
|
||||
*/
|
||||
class ExifReader {
|
||||
private static final String TAG = "ExifReader";
|
||||
|
||||
private final ExifInterface mInterface;
|
||||
|
||||
ExifReader( ExifInterface iRef ) {
|
||||
mInterface = iRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the inputStream and and returns the EXIF data in an
|
||||
* {@link ExifData}.
|
||||
*
|
||||
* @throws ExifInvalidFormatException
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
protected ExifData read( InputStream inputStream, int options ) throws ExifInvalidFormatException, IOException {
|
||||
ExifParser parser = ExifParser.parse( inputStream, options, mInterface );
|
||||
ExifData exifData = new ExifData( parser.getByteOrder() );
|
||||
exifData.setSections( parser.getSections() );
|
||||
exifData.mUncompressedDataPosition = parser.getUncompressedDataPosition();
|
||||
|
||||
exifData.setQualityGuess( parser.getQualityGuess() );
|
||||
exifData.setJpegProcess( parser.getJpegProcess() );
|
||||
|
||||
final int w = parser.getImageWidth();
|
||||
final int h = parser.getImageLength();
|
||||
|
||||
if( w > 0 && h > 0 ) {
|
||||
exifData.setImageSize( w, h );
|
||||
}
|
||||
|
||||
ExifTag tag;
|
||||
|
||||
int event = parser.next();
|
||||
while( event != ExifParser.EVENT_END ) {
|
||||
switch( event ) {
|
||||
case ExifParser.EVENT_START_OF_IFD:
|
||||
exifData.addIfdData( new IfdData( parser.getCurrentIfd() ) );
|
||||
break;
|
||||
case ExifParser.EVENT_NEW_TAG:
|
||||
tag = parser.getTag();
|
||||
|
||||
|
||||
|
||||
if( ! tag.hasValue() ) {
|
||||
parser.registerForTagValue( tag );
|
||||
}
|
||||
else {
|
||||
// Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
|
||||
if (parser.isDefinedTag(tag.getIfd(), tag.getTagId())) {
|
||||
exifData.getIfdData(tag.getIfd()).setTag(tag);
|
||||
}
|
||||
else {
|
||||
Log.w(TAG, "skip tag because not registered in the tag table:" + tag);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
|
||||
tag = parser.getTag();
|
||||
if( tag.getDataType() == ExifTag.TYPE_UNDEFINED ) {
|
||||
parser.readFullTagValue( tag );
|
||||
}
|
||||
exifData.getIfdData( tag.getIfd() ).setTag( tag );
|
||||
break;
|
||||
case ExifParser.EVENT_COMPRESSED_IMAGE:
|
||||
byte buf[] = new byte[parser.getCompressedImageSize()];
|
||||
if( buf.length == parser.read( buf ) ) {
|
||||
exifData.setCompressedThumbnail( buf );
|
||||
}
|
||||
else {
|
||||
Log.w( TAG, "Failed to read the compressed thumbnail" );
|
||||
}
|
||||
break;
|
||||
case ExifParser.EVENT_UNCOMPRESSED_STRIP:
|
||||
buf = new byte[parser.getStripSize()];
|
||||
if( buf.length == parser.read( buf ) ) {
|
||||
exifData.setStripBytes( parser.getStripIndex(), buf );
|
||||
}
|
||||
else {
|
||||
Log.w( TAG, "Failed to read the strip bytes" );
|
||||
}
|
||||
break;
|
||||
}
|
||||
event = parser.next();
|
||||
}
|
||||
return exifData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* This class reads the EXIF header of a JPEG file and stores it in
|
||||
* [ExifData].
|
||||
*/
|
||||
internal class ExifReader(private val mInterface : ExifInterface) {
|
||||
|
||||
/**
|
||||
* Parses the inputStream and and returns the EXIF data in an
|
||||
* [ExifData].
|
||||
*
|
||||
* @throws ExifInvalidFormatException
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
@Throws(ExifInvalidFormatException::class, IOException::class)
|
||||
fun read(inputStream : InputStream, options : Int) : ExifData {
|
||||
val parser = ExifParser.parse(inputStream, options, mInterface)
|
||||
val exifData = ExifData(parser.byteOrder !!)
|
||||
exifData.sections = parser.sections
|
||||
exifData.mUncompressedDataPosition = parser.uncompressedDataPosition
|
||||
|
||||
exifData.qualityGuess = parser.qualityGuess
|
||||
exifData.jpegProcess = parser.jpegProcess
|
||||
|
||||
val w = parser.imageWidth
|
||||
val h = parser.imageLength
|
||||
|
||||
if(w > 0 && h > 0) {
|
||||
exifData.setImageSize(w, h)
|
||||
}
|
||||
|
||||
var tag : ExifTag?
|
||||
|
||||
var event = parser.next()
|
||||
while(event != ExifParser.EVENT_END) {
|
||||
when(event) {
|
||||
ExifParser.EVENT_START_OF_IFD -> exifData.addIfdData(IfdData(parser.currentIfd))
|
||||
|
||||
ExifParser.EVENT_NEW_TAG -> {
|
||||
tag = parser.tag
|
||||
|
||||
|
||||
|
||||
if(! tag !!.hasValue()) {
|
||||
parser.registerForTagValue(tag)
|
||||
} else {
|
||||
// Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
|
||||
if(parser.isDefinedTag(tag.ifd, tag.tagId.toInt())) {
|
||||
exifData.getIfdData(tag.ifd) !!.setTag(tag)
|
||||
} else {
|
||||
Log.w(TAG, "skip tag because not registered in the tag table:$tag")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExifParser.EVENT_VALUE_OF_REGISTERED_TAG -> {
|
||||
tag = parser.tag
|
||||
if(tag !!.dataType == ExifTag.TYPE_UNDEFINED) {
|
||||
parser.readFullTagValue(tag)
|
||||
}
|
||||
exifData.getIfdData(tag.ifd) !!.setTag(tag)
|
||||
}
|
||||
|
||||
ExifParser.EVENT_COMPRESSED_IMAGE -> {
|
||||
val buf = ByteArray(parser.compressedImageSize)
|
||||
if(buf.size == parser.read(buf)) {
|
||||
exifData.compressedThumbnail = buf
|
||||
} else {
|
||||
Log.w(TAG, "Failed to read the compressed thumbnail")
|
||||
}
|
||||
}
|
||||
|
||||
ExifParser.EVENT_UNCOMPRESSED_STRIP -> {
|
||||
val buf = ByteArray(parser.stripSize)
|
||||
if(buf.size == parser.read(buf)) {
|
||||
exifData.setStripBytes(parser.stripIndex, buf)
|
||||
} else {
|
||||
Log.w(TAG, "Failed to read the strip bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
event = parser.next()
|
||||
}
|
||||
return exifData
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExifReader"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,893 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import java.nio.charset.Charset
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This class stores information of an EXIF tag. For more information about
|
||||
* defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
|
||||
* instantiated using [ExifInterface.buildTag].
|
||||
*
|
||||
* @see ExifInterface
|
||||
*/
|
||||
// Use builtTag in ExifInterface instead of constructor.
|
||||
@Suppress("unused")
|
||||
open class ExifTag internal constructor(
|
||||
|
||||
// Exif TagId. the TID of this tag.
|
||||
val tagId : Short,
|
||||
|
||||
// Exif Tag Type. the data type of this tag
|
||||
|
||||
/*
|
||||
* @see .TYPE_ASCII
|
||||
* @see .TYPE_LONG
|
||||
* @see .TYPE_RATIONAL
|
||||
* @see .TYPE_UNDEFINED
|
||||
* @see .TYPE_UNSIGNED_BYTE
|
||||
* @see .TYPE_UNSIGNED_LONG
|
||||
* @see .TYPE_UNSIGNED_RATIONAL
|
||||
* @see .TYPE_UNSIGNED_SHORT
|
||||
*/
|
||||
val dataType : Short,
|
||||
|
||||
componentCount : Int,
|
||||
|
||||
// The ifd that this tag should be put in. the ID of the IFD this tag belongs to.
|
||||
/*
|
||||
* @see IfdId.TYPE_IFD_0
|
||||
* @see IfdId.TYPE_IFD_1
|
||||
* @see IfdId.TYPE_IFD_EXIF
|
||||
* @see IfdId.TYPE_IFD_GPS
|
||||
* @see IfdId.TYPE_IFD_INTEROPERABILITY
|
||||
*/
|
||||
var ifd : Int,
|
||||
|
||||
// If tag has defined count
|
||||
private var mHasDefinedDefaultComponentCount : Boolean
|
||||
) {
|
||||
// Actual data count in tag (should be number of elements in value array)
|
||||
/**
|
||||
* Gets the component count of this tag.
|
||||
*/
|
||||
|
||||
// TODO: fix integer overflows with this
|
||||
var componentCount : Int = 0
|
||||
private set
|
||||
|
||||
// The value (array of elements of type Tag Type)
|
||||
private var mValue : Any? = null
|
||||
|
||||
// Value offset in exif header. the offset of this tag.
|
||||
// This is only valid if this data size > 4 and contains an offset to the location of the actual value.
|
||||
var offset : Int = 0
|
||||
|
||||
// the total data size in bytes of the value of this tag.
|
||||
val dataSize : Int
|
||||
get() = componentCount * getElementSize(dataType)
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value as a byte array. This method should be used for tags of
|
||||
* type [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
|
||||
*
|
||||
* @return the value as a byte array, or null if the tag's value does not
|
||||
* exist or cannot be converted to a byte array.
|
||||
*/
|
||||
val valueAsBytes : ByteArray?
|
||||
get() = mValue as? ByteArray
|
||||
|
||||
/**
|
||||
* Gets the value as an array of longs. This method should be used for tags
|
||||
* of type [.TYPE_UNSIGNED_LONG].
|
||||
*
|
||||
* @return the value as as an array of longs, or null if the tag's value
|
||||
* does not exist or cannot be converted to an array of longs.
|
||||
*/
|
||||
val valueAsLongs : LongArray?
|
||||
get() = mValue as? LongArray
|
||||
|
||||
/**
|
||||
* Gets the value as an array of Rationals. This method should be used for
|
||||
* tags of type [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
|
||||
*
|
||||
* @return the value as as an array of Rationals, or null if the tag's value
|
||||
* does not exist or cannot be converted to an array of Rationals.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val valueAsRationals : Array<Rational>?
|
||||
get() = mValue as? Array<Rational>
|
||||
|
||||
/**
|
||||
* Gets the value as an array of ints. This method should be used for tags
|
||||
* of type [.TYPE_UNSIGNED_SHORT], [.TYPE_UNSIGNED_LONG].
|
||||
*
|
||||
* @return the value as as an array of ints, or null if the tag's value does
|
||||
* not exist or cannot be converted to an array of ints.
|
||||
*/
|
||||
// Truncates
|
||||
val valueAsInts : IntArray?
|
||||
get() = when(val v = mValue){
|
||||
is LongArray-> IntArray(v.size){ v[it].toInt()}
|
||||
else ->null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value as a String. This method should be used for tags of type
|
||||
* [.TYPE_ASCII].
|
||||
*
|
||||
* @return the value as a String, or null if the tag's value does not exist
|
||||
* or cannot be converted to a String.
|
||||
*/
|
||||
val valueAsString : String?
|
||||
get() = when(val v = mValue) {
|
||||
is String -> v
|
||||
is ByteArray -> String(v, US_ASCII)
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the [.TYPE_ASCII] data.
|
||||
*
|
||||
* @throws IllegalArgumentException If the type is NOT
|
||||
* [.TYPE_ASCII].
|
||||
*/
|
||||
protected val string : String
|
||||
get() = valueAsString !!
|
||||
|
||||
/*
|
||||
* Get the converted ascii byte. Used by ExifOutputStream.
|
||||
*/
|
||||
val stringByte : ByteArray?
|
||||
get() = mValue as? ByteArray
|
||||
|
||||
init {
|
||||
this.componentCount = componentCount
|
||||
mValue = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the component count of this tag. Call this function before
|
||||
* setValue() if the length of value does not match the component count.
|
||||
*/
|
||||
fun forceSetComponentCount(count : Int) {
|
||||
componentCount = count
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this ExifTag contains value; otherwise, this tag will
|
||||
* contain an offset value that is determined when the tag is written.
|
||||
*/
|
||||
fun hasValue() : Boolean {
|
||||
return mValue != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets integer values into this tag. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_SHORT]. This method will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_SHORT],
|
||||
* [.TYPE_UNSIGNED_LONG], or [.TYPE_LONG].
|
||||
* * The value overflows.
|
||||
* * The value.length does NOT match the component count in the definition
|
||||
* for this tag.
|
||||
*
|
||||
*/
|
||||
fun setValue(value : IntArray) : Boolean {
|
||||
if(checkBadComponentCount(value.size)) {
|
||||
return false
|
||||
}
|
||||
if(dataType != TYPE_UNSIGNED_SHORT && dataType != TYPE_LONG &&
|
||||
dataType != TYPE_UNSIGNED_LONG) {
|
||||
return false
|
||||
}
|
||||
if(dataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
|
||||
return false
|
||||
} else if(dataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val data = LongArray(value.size)
|
||||
for(i in value.indices) {
|
||||
data[i] = value[i].toLong()
|
||||
}
|
||||
mValue = data
|
||||
componentCount = value.size
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets integer value into this tag. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_SHORT], or [.TYPE_LONG]. This method
|
||||
* will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_SHORT],
|
||||
* [.TYPE_UNSIGNED_LONG], or [.TYPE_LONG].
|
||||
* * The value overflows.
|
||||
* * The component count in the definition of this tag is not 1.
|
||||
*
|
||||
*/
|
||||
fun setValue(value : Int) : Boolean {
|
||||
return setValue(intArrayOf(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets long values into this tag. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_LONG]. This method will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_LONG].
|
||||
* * The value overflows.
|
||||
* * The value.length does NOT match the component count in the definition
|
||||
* for this tag.
|
||||
*
|
||||
*/
|
||||
fun setValue(value : LongArray) : Boolean {
|
||||
if(checkBadComponentCount(value.size) || dataType != TYPE_UNSIGNED_LONG) {
|
||||
return false
|
||||
}
|
||||
if(checkOverflowForUnsignedLong(value)) {
|
||||
return false
|
||||
}
|
||||
mValue = value
|
||||
componentCount = value.size
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets long values into this tag. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_LONG]. This method will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_LONG].
|
||||
* * The value overflows.
|
||||
* * The component count in the definition for this tag is not 1.
|
||||
*
|
||||
*/
|
||||
fun setValue(value : Long) : Boolean {
|
||||
return setValue(longArrayOf(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Rational values into this tag. This method should be used for tags
|
||||
* of type [.TYPE_UNSIGNED_RATIONAL], or [.TYPE_RATIONAL]. This
|
||||
* method will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_RATIONAL]
|
||||
* or [.TYPE_RATIONAL].
|
||||
* * The value overflows.
|
||||
* * The value.length does NOT match the component count in the definition
|
||||
* for this tag.
|
||||
*
|
||||
*
|
||||
* @see Rational
|
||||
*/
|
||||
fun setValue(value : Array<Rational>) : Boolean {
|
||||
if(checkBadComponentCount(value.size)) {
|
||||
return false
|
||||
}
|
||||
if(dataType != TYPE_UNSIGNED_RATIONAL && dataType != TYPE_RATIONAL) {
|
||||
return false
|
||||
}
|
||||
if(dataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
|
||||
return false
|
||||
} else if(dataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
mValue = value
|
||||
componentCount = value.size
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a Rational value into this tag. This method should be used for tags
|
||||
* of type [.TYPE_UNSIGNED_RATIONAL], or [.TYPE_RATIONAL]. This
|
||||
* method will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_RATIONAL]
|
||||
* or [.TYPE_RATIONAL].
|
||||
* * The value overflows.
|
||||
* * The component count in the definition for this tag is not 1.
|
||||
*
|
||||
*
|
||||
* @see Rational
|
||||
*/
|
||||
fun setValue(value : Rational) : Boolean =
|
||||
setValue(arrayOf(value))
|
||||
|
||||
/**
|
||||
* Sets byte values into this tag. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_BYTE] or [.TYPE_UNDEFINED]. This method
|
||||
* will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_BYTE] or
|
||||
* [.TYPE_UNDEFINED] .
|
||||
* * The length does NOT match the component count in the definition for
|
||||
* this tag.
|
||||
*
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun setValue(value : ByteArray, offset : Int = 0, length : Int = value.size) : Boolean {
|
||||
if(checkBadComponentCount(length)) {
|
||||
return false
|
||||
}
|
||||
if(dataType != TYPE_UNSIGNED_BYTE && dataType != TYPE_UNDEFINED) {
|
||||
return false
|
||||
}
|
||||
componentCount = length
|
||||
mValue = ByteArray(length).also{
|
||||
System.arraycopy(value, offset, it, 0, length)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets byte value into this tag. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_BYTE] or [.TYPE_UNDEFINED]. This method
|
||||
* will fail if:
|
||||
*
|
||||
* * The component type of this tag is not [.TYPE_UNSIGNED_BYTE] or
|
||||
* [.TYPE_UNDEFINED] .
|
||||
* * The component count in the definition for this tag is not 1.
|
||||
*
|
||||
*/
|
||||
fun setValue(value : Byte) : Boolean =
|
||||
setValue(byteArrayOf(value))
|
||||
|
||||
/**
|
||||
* Sets the value for this tag using an appropriate setValue method for the
|
||||
* given object. This method will fail if:
|
||||
*
|
||||
* * The corresponding setValue method for the class of the object passed
|
||||
* in would fail.
|
||||
* * There is no obvious way to cast the object passed in into an EXIF tag
|
||||
* type.
|
||||
*
|
||||
*/
|
||||
inline fun <reified T : Any?> setValueAny(obj : T) : Boolean {
|
||||
when(obj) {
|
||||
null -> return false
|
||||
|
||||
is String -> return setValue(obj)
|
||||
is ByteArray -> return setValue(obj)
|
||||
is IntArray -> return setValue(obj)
|
||||
is LongArray -> return setValue(obj)
|
||||
is Rational -> return setValue(obj)
|
||||
is Byte -> return setValue(obj.toByte())
|
||||
is Short -> return setValue(obj.toInt() and 0x0ffff)
|
||||
is Int -> return setValue(obj.toInt())
|
||||
is Long -> return setValue(obj.toLong())
|
||||
|
||||
else ->{
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val ra = obj as? Array<Rational>
|
||||
if(ra != null) return setValue( ra )
|
||||
|
||||
// Nulls in this array are treated as zeroes.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val sa = obj as? Array<Short?>
|
||||
if( sa != null) return setValue(IntArray(sa.size){ (sa[it]?.toInt() ?: 0) and 0xffff})
|
||||
|
||||
// Nulls in this array are treated as zeroes.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val ia = obj as? Array<Int?>
|
||||
if( ia != null) return setValue(IntArray(ia.size){ ia[it] ?: 0 })
|
||||
|
||||
// Nulls in this array are treated as zeroes.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val la = obj as? Array<Long?>
|
||||
if( la != null) return setValue(LongArray(la.size){ la[it] ?: 0L })
|
||||
|
||||
// Nulls in this array are treated as zeroes.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val ba = obj as? Array<Byte?>
|
||||
if( ba != null) return setValue(ByteArray(ba.size){ ba[it] ?: 0 })
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timestamp to this tag. The method converts the timestamp with the
|
||||
* format of "yyyy:MM:dd kk:mm:ss" and calls [.setValue]. This
|
||||
* method will fail if the data type is not [.TYPE_ASCII] or the
|
||||
* component count of this tag is not 20 or undefined.
|
||||
*
|
||||
* @param time the number of milliseconds since Jan. 1, 1970 GMT
|
||||
* @return true on success
|
||||
*/
|
||||
fun setValueTime(time : Long) : Boolean {
|
||||
// synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
|
||||
synchronized(TIME_FORMAT) {
|
||||
return setValue(TIME_FORMAT.format(Date(time)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a string value into this tag. This method should be used for tags of
|
||||
* type [.TYPE_ASCII]. The string is converted to an ASCII string.
|
||||
* Characters that cannot be converted are replaced with '?'. The length of
|
||||
* the string must be equal to either (component count -1) or (component
|
||||
* count). The final byte will be set to the string null terminator '\0',
|
||||
* overwriting the last character in the string if the value.length is equal
|
||||
* to the component count. This method will fail if:
|
||||
*
|
||||
* * The data type is not [.TYPE_ASCII] or [.TYPE_UNDEFINED].
|
||||
* * The length of the string is not equal to (component count -1) or
|
||||
* (component count) in the definition for this tag.
|
||||
*
|
||||
*/
|
||||
fun setValue(value : String) : Boolean {
|
||||
if(dataType != TYPE_ASCII && dataType != TYPE_UNDEFINED) {
|
||||
return false
|
||||
}
|
||||
|
||||
val buf = value.toByteArray(US_ASCII)
|
||||
|
||||
val finalBuf = when {
|
||||
buf.isNotEmpty() -> when {
|
||||
buf[buf.size - 1].toInt() == 0 || dataType == TYPE_UNDEFINED -> buf
|
||||
else -> buf.copyOf(buf.size + 1)
|
||||
}
|
||||
dataType == TYPE_ASCII && componentCount == 1 -> byteArrayOf(0)
|
||||
else -> buf
|
||||
}
|
||||
val count = finalBuf.size
|
||||
if(checkBadComponentCount(count)) {
|
||||
return false
|
||||
}
|
||||
componentCount = count
|
||||
mValue = finalBuf
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkBadComponentCount(count : Int) : Boolean {
|
||||
return mHasDefinedDefaultComponentCount && componentCount != count
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value as a String. This method should be used for tags of type
|
||||
* [.TYPE_ASCII].
|
||||
*
|
||||
* @param defaultValue the String to return if the tag's value does not
|
||||
* exist or cannot be converted to a String.
|
||||
* @return the tag's value as a String, or the defaultValue.
|
||||
*/
|
||||
fun getValueAsString(defaultValue : String) : String {
|
||||
return valueAsString ?: defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value as a byte. If there are more than 1 bytes in this value,
|
||||
* gets the first byte. This method should be used for tags of type
|
||||
* [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
|
||||
*
|
||||
* @param defaultValue the byte to return if tag's value does not exist or
|
||||
* cannot be converted to a byte.
|
||||
* @return the tag's value as a byte, or the defaultValue.
|
||||
*/
|
||||
fun getValueAsByte(defaultValue : Byte) : Byte {
|
||||
val array = valueAsBytes
|
||||
return if(array?.isNotEmpty()==true) array[0] else defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value as a Rational. If there are more than 1 Rationals in this
|
||||
* value, gets the first one. This method should be used for tags of type
|
||||
* [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
|
||||
*
|
||||
* @param defaultValue the numerator of the Rational to return if tag's
|
||||
* value does not exist or cannot be converted to a Rational (the
|
||||
* denominator will be 1).
|
||||
* @return the tag's value as a Rational, or the defaultValue.
|
||||
*/
|
||||
fun getValueAsRational(defaultValue : Long) : Rational =
|
||||
getValueAsRational( Rational(defaultValue, 1))
|
||||
|
||||
/**
|
||||
* Gets the value as a Rational. If there are more than 1 Rationals in this
|
||||
* value, gets the first one. This method should be used for tags of type
|
||||
* [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
|
||||
*
|
||||
* @param defaultValue the Rational to return if tag's value does not exist
|
||||
* or cannot be converted to a Rational.
|
||||
* @return the tag's value as a Rational, or the defaultValue.
|
||||
*/
|
||||
private fun getValueAsRational(defaultValue : Rational) : Rational {
|
||||
val array = valueAsRationals
|
||||
return if(array?.isNotEmpty()==true) array[0] else defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value as an int. If there are more than 1 ints in this value,
|
||||
* gets the first one. This method should be used for tags of type
|
||||
* [.TYPE_UNSIGNED_SHORT], [.TYPE_UNSIGNED_LONG].
|
||||
*
|
||||
* @param defaultValue the int to return if tag's value does not exist or
|
||||
* cannot be converted to an int.
|
||||
* @return the tag's value as a int, or the defaultValue.
|
||||
*/
|
||||
fun getValueAsInt(defaultValue : Int) : Int {
|
||||
val array = valueAsInts
|
||||
return if(array?.isNotEmpty()==true) array[0] else defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value or null if none exists. If there are more than 1 longs in
|
||||
* this value, gets the first one. This method should be used for tags of
|
||||
* type [.TYPE_UNSIGNED_LONG].
|
||||
*
|
||||
* @param defaultValue the long to return if tag's value does not exist or
|
||||
* cannot be converted to a long.
|
||||
* @return the tag's value as a long, or the defaultValue.
|
||||
*/
|
||||
fun getValueAsLong(defaultValue : Long) : Long {
|
||||
val array = valueAsLongs
|
||||
return if(array?.isNotEmpty()==true) array[0] else defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag's value or null if none exists.
|
||||
*/
|
||||
fun getValue() : Any? {
|
||||
return mValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a long representation of the value.
|
||||
*
|
||||
* @param defaultValue value to return if there is no value or value is a
|
||||
* rational with a denominator of 0.
|
||||
* @return the tag's value as a long, or defaultValue if no representation
|
||||
* exists.
|
||||
*/
|
||||
fun forceGetValueAsLong(defaultValue : Long) : Long {
|
||||
when(val v = mValue) {
|
||||
is LongArray -> if(v.isNotEmpty()) return v[0]
|
||||
is ByteArray -> if(v.isNotEmpty()) return v[0].toLong()
|
||||
|
||||
else -> {
|
||||
val r = valueAsRationals
|
||||
if(r?.isNotEmpty() == true && r[0].denominator != 0L) {
|
||||
return r[0].toDouble().toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value for type [.TYPE_ASCII], [.TYPE_LONG],
|
||||
* [.TYPE_UNDEFINED], [.TYPE_UNSIGNED_BYTE],
|
||||
* [.TYPE_UNSIGNED_LONG], or [.TYPE_UNSIGNED_SHORT]. For
|
||||
* [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL], call
|
||||
* [.getRational] instead.
|
||||
*
|
||||
* @throws IllegalArgumentException if the data type is
|
||||
* [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
|
||||
*/
|
||||
fun getValueAt(index : Int) : Long {
|
||||
return when(val v = mValue) {
|
||||
is LongArray -> v[index]
|
||||
is ByteArray -> v[index].toLong()
|
||||
else -> error(
|
||||
"Cannot get integer value from ${convertTypeToString(dataType)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL] data.
|
||||
*
|
||||
* @throws IllegalArgumentException If the type is NOT
|
||||
* [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
|
||||
*/
|
||||
fun getRational(index : Int) : Rational? {
|
||||
require(! (dataType != TYPE_RATIONAL && dataType != TYPE_UNSIGNED_RATIONAL)) {
|
||||
"Cannot get RATIONAL value from " + convertTypeToString(dataType)
|
||||
}
|
||||
return valueAsRationals?.get(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE] data.
|
||||
*
|
||||
* @param buf the byte array in which to store the bytes read.
|
||||
* @param offset the initial position in buffer to store the bytes.
|
||||
* @param length the maximum number of bytes to store in buffer. If length >
|
||||
* component count, only the valid bytes will be stored.
|
||||
* @throws IllegalArgumentException If the type is NOT
|
||||
* [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun getBytes(buf : ByteArray, offset : Int = 0, length : Int = buf.size) {
|
||||
require(! (dataType != TYPE_UNDEFINED && dataType != TYPE_UNSIGNED_BYTE)) {
|
||||
"Cannot get BYTE value from " + convertTypeToString(
|
||||
dataType
|
||||
)
|
||||
}
|
||||
System.arraycopy(
|
||||
mValue !!,
|
||||
0,
|
||||
buf,
|
||||
offset,
|
||||
if(length > componentCount) componentCount else length
|
||||
)
|
||||
}
|
||||
|
||||
var hasDefinedCount : Boolean
|
||||
get() = mHasDefinedDefaultComponentCount
|
||||
set(value){
|
||||
mHasDefinedDefaultComponentCount = value
|
||||
}
|
||||
|
||||
private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean {
|
||||
for(v in value) {
|
||||
if(v > UNSIGNED_SHORT_MAX || v < 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean {
|
||||
for(v in value) {
|
||||
if(v < 0 || v > UNSIGNED_LONG_MAX) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean {
|
||||
for(v in value) {
|
||||
if(v < 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkOverflowForUnsignedRational(value : Array<Rational>) : Boolean {
|
||||
for(v in value) {
|
||||
if(v.numerator < 0 || v.denominator < 0 || v.numerator > UNSIGNED_LONG_MAX || v.denominator > UNSIGNED_LONG_MAX) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkOverflowForRational(value : Array<Rational>) : Boolean {
|
||||
for(v in value) {
|
||||
if(v.numerator < LONG_MIN || v.denominator < LONG_MIN || v.numerator > LONG_MAX || v.denominator > LONG_MAX) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode() : Int {
|
||||
var result = tagId.toInt()
|
||||
result = 31 * result + dataType.toInt()
|
||||
result = 31 * result + ifd
|
||||
result = 31 * result + componentCount
|
||||
result = 31 * result + offset
|
||||
result = 31 * result + mHasDefinedDefaultComponentCount.hashCode()
|
||||
result = 31 * result + (mValue?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun equals(other : Any?) : Boolean {
|
||||
if(other !is ExifTag) return false
|
||||
|
||||
if(other.tagId != this.tagId
|
||||
|| other.componentCount != this.componentCount
|
||||
|| other.dataType != this.dataType
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
val va = this.mValue
|
||||
val vb = other.mValue
|
||||
|
||||
return when {
|
||||
|
||||
va == null -> vb == null
|
||||
|
||||
vb == null -> false
|
||||
|
||||
va is LongArray -> when(vb) {
|
||||
is LongArray -> Arrays.equals(va, vb)
|
||||
else -> false
|
||||
}
|
||||
|
||||
va is ByteArray -> when(vb) {
|
||||
is ByteArray -> Arrays.equals(va, vb)
|
||||
else -> false
|
||||
}
|
||||
|
||||
va is Array<*> && va.isArrayOf<Rational>() -> when {
|
||||
vb is Array<*> && vb.isArrayOf<Rational>() -> Arrays.equals(va, vb)
|
||||
else -> false
|
||||
}
|
||||
|
||||
else -> va == vb
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() : String {
|
||||
val strTagId = String.format("%04X", tagId)
|
||||
return "tag id: $strTagId\nifd id: $ifd\ntype: ${convertTypeToString(dataType)}\ncount: $componentCount\noffset: $offset\nvalue: ${forceGetValueAsString()}\n"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string representation of the value.
|
||||
*/
|
||||
private fun forceGetValueAsString() : String {
|
||||
when(val v = mValue) {
|
||||
|
||||
null -> return ""
|
||||
|
||||
is ByteArray -> return when(dataType) {
|
||||
TYPE_ASCII -> String(v, US_ASCII)
|
||||
else -> Arrays.toString(v)
|
||||
}
|
||||
|
||||
is LongArray -> return when {
|
||||
v.size == 1 -> v[0].toString()
|
||||
else -> Arrays.toString(v)
|
||||
}
|
||||
|
||||
is Array<*> -> return when {
|
||||
v.size == 1 -> v[0]?.toString() ?: ""
|
||||
else -> Arrays.toString(v)
|
||||
}
|
||||
|
||||
else -> return v.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The BYTE type in the EXIF standard. An 8-bit unsigned integer.
|
||||
*/
|
||||
const val TYPE_UNSIGNED_BYTE : Short = 1
|
||||
/**
|
||||
* The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
|
||||
* ASCII code. The final byte is terminated with NULL.
|
||||
*/
|
||||
const val TYPE_ASCII : Short = 2
|
||||
/**
|
||||
* The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
|
||||
*/
|
||||
const val TYPE_UNSIGNED_SHORT : Short = 3
|
||||
/**
|
||||
* The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
|
||||
*/
|
||||
const val TYPE_UNSIGNED_LONG : Short = 4
|
||||
/**
|
||||
* The RATIONAL type of EXIF standard. It consists of two LONGs. The first
|
||||
* one is the numerator and the second one expresses the denominator.
|
||||
*/
|
||||
const val TYPE_UNSIGNED_RATIONAL : Short = 5
|
||||
/**
|
||||
* The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
|
||||
* value depending on the field definition.
|
||||
*/
|
||||
const val TYPE_UNDEFINED : Short = 7
|
||||
/**
|
||||
* The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
|
||||
* (2's complement notation).
|
||||
*/
|
||||
const val TYPE_LONG : Short = 9
|
||||
/**
|
||||
* The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
|
||||
* one is the numerator and the second one is the denominator.
|
||||
*/
|
||||
const val TYPE_RATIONAL : Short = 10
|
||||
internal const val SIZE_UNDEFINED = 0
|
||||
private val TYPE_TO_SIZE_MAP = IntArray(11)
|
||||
private const val UNSIGNED_SHORT_MAX = 65535
|
||||
private const val UNSIGNED_LONG_MAX = 4294967295L
|
||||
private const val LONG_MAX = Integer.MAX_VALUE.toLong()
|
||||
private const val LONG_MIN = Integer.MIN_VALUE.toLong()
|
||||
|
||||
init {
|
||||
TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE.toInt()] = 1
|
||||
TYPE_TO_SIZE_MAP[TYPE_ASCII.toInt()] = 1
|
||||
TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT.toInt()] = 2
|
||||
TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG.toInt()] = 4
|
||||
TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL.toInt()] = 8
|
||||
TYPE_TO_SIZE_MAP[TYPE_UNDEFINED.toInt()] = 1
|
||||
TYPE_TO_SIZE_MAP[TYPE_LONG.toInt()] = 4
|
||||
TYPE_TO_SIZE_MAP[TYPE_RATIONAL.toInt()] = 8
|
||||
}
|
||||
|
||||
private val TIME_FORMAT = SimpleDateFormat("yyyy:MM:dd kk:mm:ss", Locale.ENGLISH)
|
||||
private val US_ASCII = Charset.forName("US-ASCII")
|
||||
|
||||
/**
|
||||
* Returns true if the given IFD is a valid IFD.
|
||||
*/
|
||||
fun isValidIfd(ifdId : Int) : Boolean =
|
||||
when(ifdId) {
|
||||
IfdId.TYPE_IFD_0,
|
||||
IfdId.TYPE_IFD_1,
|
||||
IfdId.TYPE_IFD_EXIF,
|
||||
IfdId.TYPE_IFD_INTEROPERABILITY,
|
||||
IfdId.TYPE_IFD_GPS -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a given type is a valid tag type.
|
||||
*/
|
||||
fun isValidType(type : Short) : Boolean =
|
||||
when(type) {
|
||||
TYPE_UNSIGNED_BYTE,
|
||||
TYPE_ASCII,
|
||||
TYPE_UNSIGNED_SHORT,
|
||||
TYPE_UNSIGNED_LONG,
|
||||
TYPE_UNSIGNED_RATIONAL,
|
||||
TYPE_UNDEFINED,
|
||||
TYPE_LONG,
|
||||
TYPE_RATIONAL -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the element size of the given data type in bytes.
|
||||
*
|
||||
* @see .TYPE_ASCII
|
||||
* @see .TYPE_LONG
|
||||
* @see .TYPE_RATIONAL
|
||||
* @see .TYPE_UNDEFINED
|
||||
* @see .TYPE_UNSIGNED_BYTE
|
||||
* @see .TYPE_UNSIGNED_LONG
|
||||
* @see .TYPE_UNSIGNED_RATIONAL
|
||||
* @see .TYPE_UNSIGNED_SHORT
|
||||
*/
|
||||
fun getElementSize(type : Short) : Int = TYPE_TO_SIZE_MAP[type.toInt()]
|
||||
|
||||
private fun convertTypeToString(type : Short) : String =
|
||||
when(type) {
|
||||
TYPE_UNSIGNED_BYTE -> "UNSIGNED_BYTE"
|
||||
TYPE_ASCII -> "ASCII"
|
||||
TYPE_UNSIGNED_SHORT -> "UNSIGNED_SHORT"
|
||||
TYPE_UNSIGNED_LONG -> "UNSIGNED_LONG"
|
||||
TYPE_UNSIGNED_RATIONAL -> "UNSIGNED_RATIONAL"
|
||||
TYPE_UNDEFINED -> "UNDEFINED"
|
||||
TYPE_LONG -> "LONG"
|
||||
TYPE_RATIONAL -> "RATIONAL"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Equivalent to setValue(value, 0, value.length).
|
||||
*/
|
||||
/**
|
||||
* Equivalent to getBytes(buffer, 0, buffer.length).
|
||||
*/
|
|
@ -1,33 +0,0 @@
|
|||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* Created by alessandro on 20/04/14.
|
||||
*/
|
||||
public class ExifUtil {
|
||||
|
||||
static final NumberFormat formatter = DecimalFormat.getInstance();
|
||||
|
||||
public static String processLensSpecifications( Rational[] values ) {
|
||||
Rational min_focal = values[0];
|
||||
Rational max_focal = values[1];
|
||||
Rational min_f = values[2];
|
||||
Rational max_f = values[3];
|
||||
|
||||
formatter.setMaximumFractionDigits(1);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( formatter.format( min_focal.toDouble() ) );
|
||||
sb.append( "-" );
|
||||
sb.append( formatter.format( max_focal.toDouble() ) );
|
||||
sb.append( "mm f/" );
|
||||
sb.append( formatter.format( min_f.toDouble() ) );
|
||||
sb.append( "-" );
|
||||
sb.append( formatter.format( max_f.toDouble() ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import java.text.DecimalFormat
|
||||
|
||||
/**
|
||||
* Created by alessandro on 20/04/14.
|
||||
*/
|
||||
object ExifUtil {
|
||||
|
||||
private val formatter = DecimalFormat.getInstance()
|
||||
|
||||
fun processLensSpecifications(values : Array<Rational>) : String {
|
||||
val min_focal = values[0]
|
||||
val max_focal = values[1]
|
||||
val min_f = values[2]
|
||||
val max_f = values[3]
|
||||
|
||||
formatter.maximumFractionDigits = 1
|
||||
|
||||
val sb = StringBuilder()
|
||||
sb.append(formatter.format(min_focal.toDouble()))
|
||||
sb.append("-")
|
||||
sb.append(formatter.format(max_focal.toDouble()))
|
||||
sb.append("mm f/")
|
||||
sb.append(formatter.format(min_f.toDouble()))
|
||||
sb.append("-")
|
||||
sb.append(formatter.format(max_f.toDouble()))
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class stores all the tags in an IFD.
|
||||
*
|
||||
* @see ExifData
|
||||
* @see ExifTag
|
||||
*/
|
||||
class IfdData {
|
||||
|
||||
private static final int[] sIfds = { IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF, IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS };
|
||||
private final int mIfdId;
|
||||
private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
|
||||
private int mOffsetToNextIfd = 0;
|
||||
|
||||
/**
|
||||
* Creates an IfdData with given IFD ID.
|
||||
*
|
||||
* @see IfdId#TYPE_IFD_0
|
||||
* @see IfdId#TYPE_IFD_1
|
||||
* @see IfdId#TYPE_IFD_EXIF
|
||||
* @see IfdId#TYPE_IFD_GPS
|
||||
* @see IfdId#TYPE_IFD_INTEROPERABILITY
|
||||
*/
|
||||
IfdData( int ifdId ) {
|
||||
mIfdId = ifdId;
|
||||
}
|
||||
|
||||
static protected int[] getIfds() {
|
||||
return sIfds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ExifTag} with given tag id. Return null if there is no
|
||||
* such tag.
|
||||
*/
|
||||
protected ExifTag getTag( short tagId ) {
|
||||
return mExifTags.get( tagId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or replaces a {@link ExifTag}.
|
||||
*/
|
||||
protected ExifTag setTag( ExifTag tag ) {
|
||||
tag.setIfd( mIfdId );
|
||||
return mExifTags.put( tag.getTagId(), tag );
|
||||
}
|
||||
|
||||
protected boolean checkCollision( short tagId ) {
|
||||
return mExifTags.get( tagId ) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tag of the given ID
|
||||
*/
|
||||
protected void removeTag( short tagId ) {
|
||||
mExifTags.remove( tagId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the offset of next IFD.
|
||||
*/
|
||||
protected int getOffsetToNextIfd() {
|
||||
return mOffsetToNextIfd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset of next IFD.
|
||||
*/
|
||||
protected void setOffsetToNextIfd( int offset ) {
|
||||
mOffsetToNextIfd = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all tags in this two IFDs are equal. Note that tags of
|
||||
* IFDs offset or thumbnail offset will be ignored.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals( Object obj ) {
|
||||
if( this == obj ) {
|
||||
return true;
|
||||
}
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
}
|
||||
if( obj instanceof IfdData ) {
|
||||
IfdData data = (IfdData) obj;
|
||||
if( data.getId() == mIfdId && data.getTagCount() == getTagCount() ) {
|
||||
ExifTag[] tags = data.getAllTags();
|
||||
for( ExifTag tag : tags ) {
|
||||
if( ExifInterface.isOffsetTag( tag.getTagId() ) ) {
|
||||
continue;
|
||||
}
|
||||
ExifTag tag2 = mExifTags.get( tag.getTagId() );
|
||||
if( ! tag.equals( tag2 ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tags count in the IFD.
|
||||
*/
|
||||
protected int getTagCount() {
|
||||
return mExifTags.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of this IFD.
|
||||
*
|
||||
* @see IfdId#TYPE_IFD_0
|
||||
* @see IfdId#TYPE_IFD_1
|
||||
* @see IfdId#TYPE_IFD_EXIF
|
||||
* @see IfdId#TYPE_IFD_GPS
|
||||
* @see IfdId#TYPE_IFD_INTEROPERABILITY
|
||||
*/
|
||||
protected int getId() {
|
||||
return mIfdId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a array the contains all {@link ExifTag} in this IFD.
|
||||
*/
|
||||
protected ExifTag[] getAllTags() {
|
||||
return mExifTags.values().toArray( new ExifTag[mExifTags.size()] );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import java.util.HashMap
|
||||
|
||||
|
||||
/**
|
||||
* The constants of the IFD ID defined in EXIF spec.
|
||||
*/
|
||||
object IfdId {
|
||||
const val TYPE_IFD_0 = 0
|
||||
const val TYPE_IFD_1 = 1
|
||||
const val TYPE_IFD_EXIF = 2
|
||||
const val TYPE_IFD_INTEROPERABILITY = 3
|
||||
const val TYPE_IFD_GPS = 4
|
||||
/* This is used in ExifData to allocate enough IfdData */
|
||||
const val TYPE_IFD_COUNT = 5
|
||||
}
|
||||
|
||||
|
||||
// This class stores all the tags in an IFD.
|
||||
// an IfdData with given IFD ID.
|
||||
internal class IfdData(
|
||||
// the ID of this IFD.
|
||||
val id : Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val ifds = intArrayOf(
|
||||
IfdId.TYPE_IFD_0,
|
||||
IfdId.TYPE_IFD_1,
|
||||
IfdId.TYPE_IFD_EXIF,
|
||||
IfdId.TYPE_IFD_INTEROPERABILITY,
|
||||
IfdId.TYPE_IFD_GPS
|
||||
)
|
||||
}
|
||||
|
||||
private val mExifTags = HashMap<Short, ExifTag>()
|
||||
|
||||
// the offset of next IFD.
|
||||
var offsetToNextIfd = 0
|
||||
|
||||
// the tags count in the IFD.
|
||||
val tagCount : Int
|
||||
get() = mExifTags.size
|
||||
|
||||
// a array the contains all [ExifTag] in this IFD.
|
||||
val allTags : Array<ExifTag>
|
||||
get() = mExifTags.values.toTypedArray()
|
||||
|
||||
// checkCollision
|
||||
fun contains(tagId : Short) : Boolean {
|
||||
return mExifTags[tagId] != null
|
||||
}
|
||||
|
||||
// the [ExifTag] with given tag id.
|
||||
// null if there is no such tag.
|
||||
fun getTag(tagId : Short) : ExifTag? {
|
||||
return mExifTags[tagId]
|
||||
}
|
||||
|
||||
// Adds or replaces a [ExifTag].
|
||||
fun setTag(tag : ExifTag) : ExifTag? {
|
||||
tag.ifd = id
|
||||
return mExifTags.put(tag.tagId, tag)
|
||||
}
|
||||
|
||||
// Removes the tag of the given ID
|
||||
fun removeTag(tagId : Short) {
|
||||
mExifTags.remove(tagId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all tags in this two IFDs are equal. Note that tags of
|
||||
* IFDs offset or thumbnail offset will be ignored.
|
||||
*/
|
||||
override fun equals(other : Any?) : Boolean {
|
||||
if(other === null) return false
|
||||
if(other === this) return true
|
||||
if(other is IfdData) {
|
||||
if(other.id == id && other.tagCount == tagCount) {
|
||||
val tags = other.allTags
|
||||
for(tag in tags) {
|
||||
if(ExifInterface.isOffsetTag(tag.tagId)) {
|
||||
continue
|
||||
}
|
||||
val tag2 = mExifTags[tag.tagId]
|
||||
if(tag != tag2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode() : Int {
|
||||
var result = id
|
||||
result = 31 * result + mExifTags.hashCode()
|
||||
result = 31 * result + offsetToNextIfd
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
class JpegHeader {
|
||||
/** Start Of Image **/
|
||||
public static final int TAG_SOI = 0xD8;
|
||||
|
||||
/** JFIF (JPEG File Interchange Format) */
|
||||
public static final int TAG_M_JFIF = 0xE0;
|
||||
|
||||
/** EXIF table */
|
||||
public static final int TAG_M_EXIF = 0xE1;
|
||||
|
||||
/** Product Information Comment */
|
||||
public static final int TAG_M_COM = 0xFE;
|
||||
|
||||
/** Quantization Table */
|
||||
public static final int TAG_M_DQT = 0xDB;
|
||||
|
||||
/** Start of frame */
|
||||
public static final int TAG_M_SOF0 = 0xC0;
|
||||
public static final int TAG_M_SOF1 = 0xC1;
|
||||
public static final int TAG_M_SOF2 = 0xC2;
|
||||
public static final int TAG_M_SOF3 = 0xC3;
|
||||
public static final int TAG_M_DHT = 0xC4;
|
||||
public static final int TAG_M_SOF5 = 0xC5;
|
||||
public static final int TAG_M_SOF6 = 0xC6;
|
||||
public static final int TAG_M_SOF7 = 0xC7;
|
||||
public static final int TAG_M_SOF9 = 0xC9;
|
||||
public static final int TAG_M_SOF10 = 0xCA;
|
||||
public static final int TAG_M_SOF11 = 0xCB;
|
||||
public static final int TAG_M_SOF13 = 0xCD;
|
||||
public static final int TAG_M_SOF14 = 0xCE;
|
||||
public static final int TAG_M_SOF15 = 0xCF;
|
||||
|
||||
/** Start Of Scan **/
|
||||
public static final int TAG_M_SOS = 0xDA;
|
||||
|
||||
/** End of Image */
|
||||
public static final int TAG_M_EOI = 0xD9;
|
||||
|
||||
public static final int TAG_M_IPTC = 0xED;
|
||||
|
||||
/** default JFIF Header bytes */
|
||||
public static final byte JFIF_HEADER[] = {
|
||||
(byte) 0xff, (byte) JpegHeader.TAG_M_JFIF,
|
||||
0x00, 0x10, 'J', 'F', 'I', 'F',
|
||||
0x00, 0x01, 0x01, 0x01, 0x01, 0x2C, 0x01,
|
||||
0x2C, 0x00, 0x00
|
||||
};
|
||||
|
||||
|
||||
public static final short SOI = (short) 0xFFD8;
|
||||
public static final short M_EXIF = (short) 0xFFE1;
|
||||
public static final short M_JFIF = (short) 0xFFE0;
|
||||
public static final short M_EOI = (short) 0xFFD9;
|
||||
|
||||
/**
|
||||
* SOF (start of frame). All value between M_SOF0 and SOF15 is SOF marker except for M_DHT, JPG,
|
||||
* and DAC marker.
|
||||
*/
|
||||
public static final short M_SOF0 = (short) 0xFFC0;
|
||||
public static final short M_SOF1 = (short) 0xFFC1;
|
||||
public static final short M_SOF2 = (short) 0xFFC2;
|
||||
public static final short M_SOF3 = (short) 0xFFC3;
|
||||
public static final short M_SOF5 = (short) 0xFFC5;
|
||||
public static final short M_SOF6 = (short) 0xFFC6;
|
||||
public static final short M_SOF7 = (short) 0xFFC7;
|
||||
public static final short M_SOF9 = (short) 0xFFC9;
|
||||
public static final short M_SOF10 = (short) 0xFFCA;
|
||||
public static final short M_SOF11 = (short) 0xFFCB;
|
||||
public static final short M_SOF13 = (short) 0xFFCD;
|
||||
public static final short M_SOF14 = (short) 0xFFCE;
|
||||
public static final short M_SOF15 = (short) 0xFFCF;
|
||||
public static final short M_DHT = (short) 0xFFC4;
|
||||
public static final short JPG = (short) 0xFFC8;
|
||||
public static final short DAC = (short) 0xFFCC;
|
||||
|
||||
/** Define quantization table */
|
||||
public static final short M_DQT = (short) 0xFFDB;
|
||||
|
||||
/** IPTC marker */
|
||||
public static final short M_IPTC = (short) 0xFFED;
|
||||
|
||||
/** Start of scan (begins compressed data */
|
||||
public static final short M_SOS = (short) 0xFFDA;
|
||||
|
||||
/** Comment section * */
|
||||
public static final short M_COM = (short) 0xFFFE; // Comment section
|
||||
|
||||
public static final boolean isSofMarker( short marker ) {
|
||||
return marker >= M_SOF0 && marker <= M_SOF15 && marker != M_DHT && marker != JPG && marker != DAC;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
object JpegHeader {
|
||||
/** Start Of Image */
|
||||
const val TAG_SOI = 0xD8
|
||||
|
||||
/** JFIF (JPEG File Interchange Format) */
|
||||
const val TAG_M_JFIF = 0xE0
|
||||
|
||||
/** EXIF table */
|
||||
const val TAG_M_EXIF = 0xE1
|
||||
|
||||
/** Product Information Comment */
|
||||
const val TAG_M_COM = 0xFE
|
||||
|
||||
/** Quantization Table */
|
||||
const val TAG_M_DQT = 0xDB
|
||||
|
||||
/** Start of frame */
|
||||
const val TAG_M_SOF0 = 0xC0
|
||||
const val TAG_M_SOF1 = 0xC1
|
||||
const val TAG_M_SOF2 = 0xC2
|
||||
const val TAG_M_SOF3 = 0xC3
|
||||
const val TAG_M_DHT = 0xC4
|
||||
const val TAG_M_SOF5 = 0xC5
|
||||
const val TAG_M_SOF6 = 0xC6
|
||||
const val TAG_M_SOF7 = 0xC7
|
||||
const val TAG_M_SOF9 = 0xC9
|
||||
const val TAG_M_SOF10 = 0xCA
|
||||
const val TAG_M_SOF11 = 0xCB
|
||||
const val TAG_M_SOF13 = 0xCD
|
||||
const val TAG_M_SOF14 = 0xCE
|
||||
const val TAG_M_SOF15 = 0xCF
|
||||
|
||||
/** Start Of Scan */
|
||||
const val TAG_M_SOS = 0xDA
|
||||
|
||||
/** End of Image */
|
||||
const val TAG_M_EOI = 0xD9
|
||||
|
||||
const val TAG_M_IPTC = 0xED
|
||||
|
||||
/** default JFIF Header bytes */
|
||||
val JFIF_HEADER = byteArrayOf(
|
||||
0xff.toByte(),
|
||||
TAG_M_JFIF.toByte(),
|
||||
0x00,
|
||||
0x10,
|
||||
'J'.toByte(),
|
||||
'F'.toByte(),
|
||||
'I'.toByte(),
|
||||
'F'.toByte(),
|
||||
0x00,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x2C,
|
||||
0x01,
|
||||
0x2C,
|
||||
0x00,
|
||||
0x00
|
||||
)
|
||||
|
||||
const val SOI = 0xFFD8.toShort()
|
||||
const val M_EXIF = 0xFFE1.toShort()
|
||||
const val M_JFIF = 0xFFE0.toShort()
|
||||
const val M_EOI = 0xFFD9.toShort()
|
||||
|
||||
/**
|
||||
* SOF (start of frame). All value between M_SOF0 and SOF15 is SOF marker except for M_DHT, JPG,
|
||||
* and DAC marker.
|
||||
*/
|
||||
const val M_SOF0 = 0xFFC0.toShort()
|
||||
const val M_SOF1 = 0xFFC1.toShort()
|
||||
const val M_SOF2 = 0xFFC2.toShort()
|
||||
const val M_SOF3 = 0xFFC3.toShort()
|
||||
const val M_SOF5 = 0xFFC5.toShort()
|
||||
const val M_SOF6 = 0xFFC6.toShort()
|
||||
const val M_SOF7 = 0xFFC7.toShort()
|
||||
const val M_SOF9 = 0xFFC9.toShort()
|
||||
const val M_SOF10 = 0xFFCA.toShort()
|
||||
const val M_SOF11 = 0xFFCB.toShort()
|
||||
const val M_SOF13 = 0xFFCD.toShort()
|
||||
const val M_SOF14 = 0xFFCE.toShort()
|
||||
const val M_SOF15 = 0xFFCF.toShort()
|
||||
const val M_DHT = 0xFFC4.toShort()
|
||||
const val JPG = 0xFFC8.toShort()
|
||||
const val DAC = 0xFFCC.toShort()
|
||||
|
||||
/** Define quantization table */
|
||||
const val M_DQT = 0xFFDB.toShort()
|
||||
|
||||
/** IPTC marker */
|
||||
const val M_IPTC = 0xFFED.toShort()
|
||||
|
||||
/** Start of scan (begins compressed data */
|
||||
const val M_SOS = 0xFFDA.toShort()
|
||||
|
||||
/** Comment section * */
|
||||
const val M_COM = 0xFFFE.toShort() // Comment section
|
||||
|
||||
fun isSofMarker(marker : Short) : Boolean {
|
||||
return marker >= M_SOF0 && marker <= M_SOF15 && marker != M_DHT && marker != JPG && marker != DAC
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
class OrderedDataOutputStream extends FilterOutputStream {
|
||||
private final ByteBuffer mByteBuffer = ByteBuffer.allocate( 4 );
|
||||
|
||||
public OrderedDataOutputStream( OutputStream out ) {
|
||||
super( out );
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream setByteOrder( ByteOrder order ) {
|
||||
mByteBuffer.order( order );
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream writeShort( short value ) throws IOException {
|
||||
mByteBuffer.rewind();
|
||||
mByteBuffer.putShort( value );
|
||||
out.write( mByteBuffer.array(), 0, 2 );
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream writeRational( Rational rational ) throws IOException {
|
||||
writeInt( (int) rational.getNumerator() );
|
||||
writeInt( (int) rational.getDenominator() );
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream writeInt( int value ) throws IOException {
|
||||
mByteBuffer.rewind();
|
||||
mByteBuffer.putInt( value );
|
||||
out.write( mByteBuffer.array() );
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
import java.io.FilterOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
internal class OrderedDataOutputStream(out : OutputStream) : FilterOutputStream(out) {
|
||||
private val mByteBuffer = ByteBuffer.allocate(4)
|
||||
|
||||
fun setByteOrder(order : ByteOrder) : OrderedDataOutputStream {
|
||||
mByteBuffer.order(order)
|
||||
return this
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeShort(value : Short) : OrderedDataOutputStream {
|
||||
mByteBuffer.rewind()
|
||||
mByteBuffer.putShort(value)
|
||||
out.write(mByteBuffer.array(), 0, 2)
|
||||
return this
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeRational(rational : Rational) : OrderedDataOutputStream {
|
||||
writeInt(rational.numerator.toInt())
|
||||
writeInt(rational.denominator.toInt())
|
||||
return this
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeInt(value : Int) : OrderedDataOutputStream {
|
||||
mByteBuffer.rewind()
|
||||
mByteBuffer.putInt(value)
|
||||
out.write(mByteBuffer.array())
|
||||
return this
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2;
|
||||
|
||||
/**
|
||||
* The rational data type of EXIF tag. Contains a pair of longs representing the
|
||||
* numerator and denominator of a Rational number.
|
||||
*/
|
||||
public class Rational {
|
||||
|
||||
private final long mNumerator;
|
||||
private final long mDenominator;
|
||||
|
||||
/**
|
||||
* Create a Rational with a given numerator and denominator.
|
||||
*
|
||||
* @param nominator
|
||||
* @param denominator
|
||||
*/
|
||||
public Rational( long nominator, long denominator ) {
|
||||
mNumerator = nominator;
|
||||
mDenominator = denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of a Rational.
|
||||
*/
|
||||
public Rational( Rational r ) {
|
||||
mNumerator = r.mNumerator;
|
||||
mDenominator = r.mDenominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the numerator of the rational.
|
||||
*/
|
||||
public long getNumerator() {
|
||||
return mNumerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the denominator of the rational
|
||||
*/
|
||||
public long getDenominator() {
|
||||
return mDenominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rational value as type double. Will cause a divide-by-zero error
|
||||
* if the denominator is 0.
|
||||
*/
|
||||
public double toDouble() {
|
||||
return mNumerator / (double) mDenominator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object obj ) {
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
}
|
||||
if( this == obj ) {
|
||||
return true;
|
||||
}
|
||||
if( obj instanceof Rational ) {
|
||||
Rational data = (Rational) obj;
|
||||
return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mNumerator + "/" + mDenominator;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package it.sephiroth.android.library.exif2
|
||||
|
||||
/**
|
||||
* The rational data type of EXIF tag. Contains a pair of longs representing the
|
||||
* numerator and denominator of a Rational number.
|
||||
*/
|
||||
class Rational(
|
||||
val numerator : Long=0,//the numerator of the rational.
|
||||
val denominator : Long =1//the denominator of the rational
|
||||
|
||||
) {
|
||||
// copy from a Rational.
|
||||
constructor(r : Rational) :this(
|
||||
numerator = r.numerator,
|
||||
denominator = r.denominator
|
||||
)
|
||||
|
||||
// Gets the rational value as type double.
|
||||
// Will cause a divide-by-zero error if the denominator is 0.
|
||||
fun toDouble() : Double = numerator.toDouble() / denominator.toDouble()
|
||||
|
||||
override fun toString() : String = "$numerator/$denominator"
|
||||
|
||||
override fun equals(other : Any?) : Boolean {
|
||||
return when {
|
||||
other === null -> false
|
||||
other === this -> true
|
||||
other is Rational -> numerator == other.numerator && denominator == other.denominator
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun hashCode() : Int =
|
||||
31 * numerator.hashCode() + denominator.hashCode()
|
||||
}
|
Loading…
Reference in New Issue