Compare commits

...

3 Commits

Author SHA1 Message Date
tateisu 77d780881c v5.543 2024-01-03 00:26:50 +09:00
tateisu 3c975f40ee fix #258, TTS読み上げ完了のブロードキャスト受信のAndroid 14対応漏れ 2024-01-03 00:25:58 +09:00
tateisu f4093abba9 コード整理 2024-01-03 00:18:09 +09:00
11 changed files with 124 additions and 85 deletions

View File

@ -4,10 +4,19 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT">
<builds>
<build path="$PROJECT_DIR$/buildSrc" name="buildSrc">
<projects>
<project path="$PROJECT_DIR$/buildSrc" />
</projects>
</build>
</builds>
</compositeBuild>
</compositeConfiguration>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="zulu-17" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@ -23,6 +32,7 @@
<option value="$PROJECT_DIR$/sample_apng" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

View File

@ -25,8 +25,8 @@ android {
defaultConfig {
targetSdk = Vers.stTargetSdkVersion
minSdk = Vers.stMinSdkVersion
versionCode = 543
versionName = "5.543"
versionCode = 544
versionName = "5.544"
applicationId = "jp.juggler.subwaytooter"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View File

@ -87,6 +87,7 @@
<application
android:name=".App1"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_spec"
android:icon="@mipmap/ic_launcher"

View File

@ -20,8 +20,8 @@ class ActCallback : AppCompatActivity() {
companion object {
private val log = LogCategory("ActCallback")
internal val last_uri = AtomicReference<Uri>(null)
internal val sent_intent = AtomicReference<Intent>(null)
internal val lastUri = AtomicReference<Uri>(null)
internal val sharedIntent = AtomicReference<Intent>(null)
private fun String?.isMediaMimeType() = when {
this == null -> false
@ -47,10 +47,10 @@ class ActCallback : AppCompatActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
log.d("onCreate flags=0x${intent.flags.toString(radix = 16)}")
var intent = this.intent
log.d("onCreate flags=0x${intent?.flags?.toString(radix = 16)}")
super.onCreate(savedInstanceState)
var intent: Intent? = intent
when {
intent == null -> {
// 多分起きないと思う
@ -65,29 +65,31 @@ class ActCallback : AppCompatActivity() {
val type = intent.type
// ACTION_SEND か ACTION_SEND_MULTIPLE
// ACTION_VIEW かつ type が 画像かビデオか音声
if (
when {
Intent.ACTION_SEND == action ||
Intent.ACTION_SEND_MULTIPLE == action ||
(Intent.ACTION_VIEW == action && type.isMediaMimeType())
) {
Intent.ACTION_SEND_MULTIPLE == action ||
(Intent.ACTION_VIEW == action && type.isMediaMimeType()) -> {
// Google Photo などから送られるIntentに含まれるuriの有効期間はActivityが閉じられるまで
// http://qiita.com/pside/items/a821e2fe9ae6b7c1a98c
// Google Photo などから送られるIntentに含まれるuriの有効期間はActivityが閉じられるまで
// http://qiita.com/pside/items/a821e2fe9ae6b7c1a98c
// 有効期間を延長する
intent = remake(intent)
if (intent != null) {
sent_intent.set(intent)
// 有効期間を延長する
intent = remake(intent)
if (intent != null) {
sharedIntent.set(intent)
}
}
} else if (forbidUriFromApp(intent)) {
// last_uriをクリアする
last_uri.set(null)
// ダイアログを閉じるまで画面遷移しない
return
} else {
val uri = intent.data
if (uri != null) {
last_uri.set(uri)
forbidUriFromApp(intent) -> {
// last_uriをクリアする
lastUri.set(null)
// ダイアログを閉じるまで画面遷移しない
return
}
else -> {
val uri = intent.data
if (uri != null) {
lastUri.set(uri)
}
}
}
}
@ -105,13 +107,11 @@ class ActCallback : AppCompatActivity() {
}
private fun copyExtraTexts(dst: Intent, src: Intent) {
var sv: String?
src.string(Intent.EXTRA_TEXT)
?.let { dst.putExtra(Intent.EXTRA_TEXT, it) }
//
sv = src.string(Intent.EXTRA_TEXT)
if (sv != null) dst.putExtra(Intent.EXTRA_TEXT, sv)
//
sv = src.string(Intent.EXTRA_SUBJECT)
if (sv != null) dst.putExtra(Intent.EXTRA_SUBJECT, sv)
src.string(Intent.EXTRA_SUBJECT)
?.let { dst.putExtra(Intent.EXTRA_SUBJECT, it) }
}
private fun remake(src: Intent): Intent? {
@ -123,52 +123,56 @@ class ActCallback : AppCompatActivity() {
val type = src.type
if (type.isMediaMimeType()) {
if (Intent.ACTION_VIEW == action) {
src.data?.let { uriOriginal ->
when (action){
Intent.ACTION_VIEW -> {
src.data?.let { uriOriginal ->
try {
val uri = saveToCache(uriOriginal)
val dst = Intent(action)
dst.setDataAndType(uri, type)
copyExtraTexts(dst, src)
return dst
} catch (ex: Throwable) {
log.e(ex, "remake failed. src=$src")
}
}
}
Intent.ACTION_SEND -> {
var uri = src.getStreamUriExtra()
?: return src // text/plainの場合
try {
val uri = saveToCache(uriOriginal)
uri = saveToCache(uri)
val dst = Intent(action)
dst.setDataAndType(uri, type)
dst.type = type
dst.putExtra(Intent.EXTRA_STREAM, uri)
copyExtraTexts(dst, src)
return dst
} catch (ex: Throwable) {
log.e(ex, "remake failed. src=$src")
}
}
} else if (Intent.ACTION_SEND == action) {
var uri = src.getStreamUriExtra()
?: return src // text/plainの場合
try {
uri = saveToCache(uri)
val dst = Intent(action)
dst.type = type
dst.putExtra(Intent.EXTRA_STREAM, uri)
copyExtraTexts(dst, src)
return dst
} catch (ex: Throwable) {
log.e(ex, "remake failed. src=$src")
}
} else if (Intent.ACTION_SEND_MULTIPLE == action) {
val listUri = src.getStreamUriListExtra()
?: return null
val listDst = ArrayList<Uri>()
for (uriOriginal in listUri) {
if (uriOriginal != null) {
try {
val uri = saveToCache(uriOriginal)
listDst.add(uri)
} catch (ex: Throwable) {
log.e(ex, "remake failed. src=$src")
Intent.ACTION_SEND_MULTIPLE -> {
val listUri = src.getStreamUriListExtra()
?: return null
val listDst = ArrayList<Uri>()
for (uriOriginal in listUri) {
if (uriOriginal != null) {
try {
val uri = saveToCache(uriOriginal)
listDst.add(uri)
} catch (ex: Throwable) {
log.e(ex, "remake failed. src=$src")
}
}
}
if (listDst.isEmpty()) return null
val dst = Intent(action)
dst.type = type
dst.putParcelableArrayListExtra(Intent.EXTRA_STREAM, listDst)
copyExtraTexts(dst, src)
return dst
}
if (listDst.isEmpty()) return null
val dst = Intent(action)
dst.type = type
dst.putParcelableArrayListExtra(Intent.EXTRA_STREAM, listDst)
copyExtraTexts(dst, src)
return dst
}
} else if (Intent.ACTION_SEND == action) {

View File

@ -657,13 +657,13 @@ class ActMain : AppCompatActivity(),
}
// 外部から受け取ったUriの処理
val uri = ActCallback.last_uri.getAndSet(null)
val uri = ActCallback.lastUri.getAndSet(null)
if (uri != null) {
handleIntentUri(uri)
}
// 外部から受け取ったUriの処理
val intent = ActCallback.sent_intent.getAndSet(null)
val intent = ActCallback.sharedIntent.getAndSet(null)
if (intent != null) {
handleSharedIntent(intent)
}

View File

@ -13,6 +13,7 @@ import android.os.Environment
import android.os.SystemClock
import android.view.View
import android.view.Window
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.media3.common.MediaItem
@ -20,6 +21,7 @@ import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.Timeline
import androidx.media3.common.util.RepeatModeUtil
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.LoadEventInfo
import androidx.media3.exoplayer.source.MediaLoadData
@ -60,7 +62,6 @@ import javax.net.ssl.HttpsURLConnection
import kotlin.math.max
import kotlin.math.min
@androidx.annotation.OptIn(markerClass = [androidx.media3.common.util.UnstableApi::class])
class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
companion object {
@ -226,6 +227,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
}
@UnstableApi
private val mediaSourceEventListener = object : MediaSourceEventListener {
override fun onLoadStarted(
windowIndex: Int,
@ -306,7 +308,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
this.showDescription = intent.getBooleanExtra(EXTRA_SHOW_DESCRIPTION, showDescription)
this.serviceType = ServiceType.values()[
this.serviceType = ServiceType.entries[
savedInstanceState?.int(EXTRA_SERVICE_TYPE)
?: intent.int(EXTRA_SERVICE_TYPE) ?: 0
]
@ -349,7 +351,8 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
)
}
internal fun initUI() {
@OptIn(UnstableApi::class)
private fun initUI() {
setContentView(views.root)
views.pbvImage.background = MediaBackgroundDrawable(
@ -409,6 +412,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
exoPlayer = ExoPlayer.Builder(this).build()
exoPlayer.addListener(playerListener)
views.pvVideo.run {
player = exoPlayer
controllerAutoShow = false
@ -916,7 +920,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
private fun mediaBackgroundDialog() {
launchAndShowError {
actionsDialog(getString(R.string.background_pattern)) {
for (k in MediaBackgroundDrawable.Kind.values()) {
for (k in MediaBackgroundDrawable.Kind.entries) {
if (!k.isMediaBackground) continue
action(k.name) {
val idx = k.toIndex()

View File

@ -10,6 +10,7 @@ import android.os.SystemClock
import android.speech.tts.TextToSpeech
import android.speech.tts.Voice
import android.text.Spannable
import androidx.core.content.ContextCompat
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.column.Column
import jp.juggler.subwaytooter.column.ColumnEncoder
@ -190,9 +191,9 @@ class AppState(
private val ttsReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent != null) {
if (TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED == intent.action) {
log.d("tts_receiver: speech completed.")
log.i("ttsReceiver onReceive action=${intent?.action}")
when (intent?.action) {
TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED -> {
ttsSpeakEnd = SystemClock.elapsedRealtime()
handler.post(procFlushSpeechQueue)
}
@ -411,7 +412,7 @@ class AppState(
} catch (ignored: Throwable) {
null
}
if (voiceSet == null || voiceSet.isEmpty()) {
if (voiceSet.isNullOrEmpty()) {
log.d("TextToSpeech.getVoices returns null or empty set.")
} else {
val lang = defaultLocale(context).toLanguageTag()
@ -427,9 +428,13 @@ class AppState(
handler.post(procFlushSpeechQueue)
context.registerReceiver(
ContextCompat.registerReceiver(
context,
ttsReceiver,
IntentFilter(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED)
IntentFilter(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED),
ContextCompat.RECEIVER_EXPORTED,
// RECEIVER_NOT_EXPORTED だと読み上げ完了を受け取れない
// ContextCompat.RECEIVER_NOT_EXPORTED,
)
// tts.setOnUtteranceProgressListener( new UtteranceProgressListener() {

View File

@ -21,7 +21,7 @@ import jp.juggler.subwaytooter.calcIconRound
import jp.juggler.subwaytooter.defaultColorIcon
import jp.juggler.subwaytooter.dialog.actionsDialog
import jp.juggler.subwaytooter.dialog.decodeAttachmentBitmap
import jp.juggler.subwaytooter.dialog.dialogArrachmentRearrange
import jp.juggler.subwaytooter.dialog.dialogAttachmentRearrange
import jp.juggler.subwaytooter.dialog.focusPointDialog
import jp.juggler.subwaytooter.dialog.showTextInputDialog
import jp.juggler.subwaytooter.pref.PrefB
@ -454,7 +454,7 @@ fun ActPost.onPickCustomThumbnailImpl(pa: PostAttachment, src: GetContentResultE
fun ActPost.rearrangeAttachments() = lifecycleScope.launch {
try {
val rearranged = dialogArrachmentRearrange(attachmentList)
val rearranged = dialogAttachmentRearrange(attachmentList)
// 入れ替え中にアップロード失敗などで要素が消えることがあるので
// 最新のattachmentListを指定順に並べ替える
val remain = ArrayList(attachmentList)

View File

@ -31,7 +31,7 @@ private val log = LogCategory("DlgAttachmentRearrange")
* 投稿画面で添付メディアを並べ替えるダイアログを開きOKボタンが押されるまで非同期待機する
* OK以外の方法で閉じたらCancellationExceptionを投げる
*/
suspend fun AppCompatActivity.dialogArrachmentRearrange(
suspend fun AppCompatActivity.dialogAttachmentRearrange(
initialList: List<PostAttachment>,
): List<PostAttachment> = suspendCancellableCoroutine { cont ->
val views = AttachmentRearrangeDialogBinding.inflate(layoutInflater)

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="device.xml"/>
<exclude
domain="sharedpref"
path="device.xml" />
</full-backup-content>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude
domain="sharedpref"
path="device.xml" />
</cloud-backup>
<device-transfer>
<exclude
domain="sharedpref"
path="device.xml" />
</device-transfer>
</data-extraction-rules>