Create hh export service with notification when done

hh
Laurent 5 years ago
parent a5c95d2131
commit 1907b97d59
  1. 4
      app/src/main/AndroidManifest.xml
  2. 75
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryActivity.kt
  3. 107
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  4. 41
      app/src/main/java/net/pokeranalytics/android/util/NotificationScheduling.kt
  5. 11
      app/src/main/java/net/pokeranalytics/android/util/video/MMediaMuxer.kt
  6. 4
      app/src/main/res/values/strings.xml

@ -168,13 +168,15 @@
android:theme="@style/PokerAnalyticsTheme.AlertDialog" android:theme="@style/PokerAnalyticsTheme.AlertDialog"
android:launchMode="singleTop"/> android:launchMode="singleTop"/>
<service android:name="net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService" android:exported="false"/>
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data

@ -1,8 +1,13 @@
package net.pokeranalytics.android.ui.modules.handhistory package net.pokeranalytics.android.ui.modules.handhistory
import android.Manifest import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle import android.os.Bundle
import android.os.IBinder
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -10,16 +15,15 @@ import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.extensions.toByteArray import net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService
import net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayerAnimator
import net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayerFragment import net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayerFragment
import net.pokeranalytics.android.ui.modules.handhistory.replayer.TableDrawer
import net.pokeranalytics.android.util.video.MMediaMuxer
import timber.log.Timber import timber.log.Timber
class HandHistoryActivity : BaseActivity() { class HandHistoryActivity : BaseActivity() {
private var replayExportService: ReplayExportService? = null
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {
IDENTIFIER("identifier"), IDENTIFIER("identifier"),
SESSION_CONFIGURATION("session_id"), SESSION_CONFIGURATION("session_id"),
@ -45,13 +49,33 @@ class HandHistoryActivity : BaseActivity() {
} }
/** Defines callbacks for service binding, passed to bindService() */
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
replayExportService = null
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as ReplayExportService.LocalBinder
replayExportService = binder.getService()
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hand_history) setContentView(R.layout.activity_hand_history)
initData()
initUI() initUI()
} }
private fun initData() {
val intent = Intent(this, ReplayExportService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
/** /**
* Init UI * Init UI
*/ */
@ -89,12 +113,6 @@ class HandHistoryActivity : BaseActivity() {
val shouldShowDataLossWarning = ((this.fragment as? HandHistoryFragment)?.isEditing == true) val shouldShowDataLossWarning = ((this.fragment as? HandHistoryFragment)?.isEditing == true)
// this.fragment?.let { hhFragment ->
// if ((hhFragment as? HandHistoryFragment)?.isEditing == true) {
// shouldShowDataLossWarning = true
// }
// }
if (shouldShowDataLossWarning) { if (shouldShowDataLossWarning) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
@ -142,6 +160,9 @@ class HandHistoryActivity : BaseActivity() {
} }
private fun videoExportAskForPermission() { private fun videoExportAskForPermission() {
Toast.makeText(this, R.string.video_export_started, Toast.LENGTH_LONG).show()
askForPermission(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), RequestCode.PERMISSION_WRITE_EXTERNAL_STORAGE.value) { granted -> askForPermission(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), RequestCode.PERMISSION_WRITE_EXTERNAL_STORAGE.value) { granted ->
if (granted) { if (granted) {
videoExport() videoExport()
@ -151,34 +172,11 @@ class HandHistoryActivity : BaseActivity() {
private fun videoExport() { private fun videoExport() {
val animator = ReplayerAnimator(this.handHistory, true) val handHistoryId = intent.getStringExtra(IntentKey.IDENTIFIER.keyName)
this.replayExportService?.export(handHistoryId) ?: run {
val square = 1024f Toast.makeText(this, "Export service not available. Please contact support", Toast.LENGTH_LONG).show()
val width = square
val height = square
animator.setDimension(width, height)
TableDrawer.configurePaints(this, animator)
val muxer = MMediaMuxer()
muxer.Init(null, width.toInt(), height.toInt(), "hhVideo", "YES!")
animator.frames(this) { bitmap, count ->
try {
val byteArray = bitmap.toByteArray()
muxer.AddFrame(byteArray, count, false)
} catch (e: Exception) {
Timber.e("error = ${e.message}")
}
} }
muxer.CreateVideo()
val path = muxer.GetPath()
Timber.d("**** Video path = $path")
} }
private fun gifExport() { private fun gifExport() {
@ -198,4 +196,9 @@ class HandHistoryActivity : BaseActivity() {
} }
override fun onDestroy() {
super.onDestroy()
unbindService(connection)
}
} }

@ -0,0 +1,107 @@
package net.pokeranalytics.android.ui.modules.handhistory.replayer
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.IBinder
import androidx.core.content.FileProvider
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.extensions.toByteArray
import net.pokeranalytics.android.util.TriggerNotification
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.video.MMediaMuxer
import timber.log.Timber
import java.io.File
class ReplayExportService : Service() {
private lateinit var handHistoryId: String
private val binder = LocalBinder()
override fun onBind(intent: Intent?): IBinder? {
return binder
}
inner class LocalBinder : Binder() {
fun getService(): ReplayExportService = this@ReplayExportService
}
fun export(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
export()
}
private fun export() {
val realm = Realm.getDefaultInstance()
realm.findById<HandHistory>(this.handHistoryId)?.let { hh ->
startExport(hh)
} ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
}
private fun startExport(handHistory: HandHistory) {
val animator = ReplayerAnimator(handHistory, true)
val square = 1024f
val width = square
val height = square
animator.setDimension(width, height)
TableDrawer.configurePaints(this, animator)
val muxer = MMediaMuxer()
muxer.Init(null, width.toInt(), height.toInt(), "hhVideo", "YES!")
animator.frames(this) { bitmap, count ->
try {
val byteArray = bitmap.toByteArray()
muxer.AddFrame(byteArray, count, false)
} catch (e: Exception) {
Timber.e("error = ${e.message}")
}
}
muxer.CreateVideo { path ->
notifyUser(path)
}
}
private fun notifyUser(path: String) {
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + path
val uri = FileProvider.getUriForFile(
this,
this.applicationContext.packageName.toString() + ".provider",
File(path)
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "video/*")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with))
val pendingIntent = PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT)
TriggerNotification(this, title, body, pendingIntent)
}
}

@ -3,6 +3,7 @@ package net.pokeranalytics.android.util
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.media.RingtoneManager import android.media.RingtoneManager
@ -37,10 +38,28 @@ class NotificationSchedule(var context: Context, var params: WorkerParameters) :
} }
} }
class TriggerNotification(context: Context, title: String, body: String) { class TriggerNotification(context: Context, title: String, body: String, intent: PendingIntent? = null) {
init { init {
sendNotification(context, title, body) sendNotification(context, title, body, intent)
}
private fun sendNotification(context: Context, title: String, body: String, intent: PendingIntent? = null) {
val notificationManager = NotificationManagerCompat.from(context)
val mBuilder = NotificationCompat.Builder(context, createNotificationChannel(context, title, body))
val notificationId = (System.currentTimeMillis() and 0xfffffff).toInt()
mBuilder.setDefaults(Notification.DEFAULT_ALL)
.setContentTitle(title)
.setContentText(body)
.setContentIntent(intent)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.release_note_icon)
.setAutoCancel(true)
notificationManager.notify(notificationId, mBuilder.build())
} }
private fun createNotificationChannel(context: Context, name: String, description: String): String { private fun createNotificationChannel(context: Context, name: String, description: String): String {
@ -63,22 +82,4 @@ class TriggerNotification(context: Context, title: String, body: String) {
return channelId return channelId
} }
private fun sendNotification(context: Context, title: String, body: String) {
val notificationManager = NotificationManagerCompat.from(context)
val mBuilder = NotificationCompat.Builder(context, createNotificationChannel(context, title, body))
val notificationId = (System.currentTimeMillis() and 0xfffffff).toInt()
mBuilder.setDefaults(Notification.DEFAULT_ALL)
.setContentTitle(title)
.setContentText(body)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.release_note_icon)
// .setContentInfo("Content Info")
.setAutoCancel(true)
notificationManager.notify(notificationId, mBuilder.build())
}
} }

@ -33,6 +33,7 @@ class MMediaMuxer {
private var pd: ProgressDialog? = null private var pd: ProgressDialog? = null
private var _title: String? = null private var _title: String? = null
private var _mess: String? = null private var _mess: String? = null
private var completion: ((String) -> (Unit))? = null
fun Init( fun Init(
activity: Activity?, activity: Activity?,
@ -82,7 +83,8 @@ class MMediaMuxer {
} }
} }
fun CreateVideo() { fun CreateVideo(handler: (String) -> (Unit)) {
this.completion = handler
currentIndexFrame = 0 currentIndexFrame = 0
Logd("Prepare Frames Data") Logd("Prepare Frames Data")
bitFirst!!.addAll(bitList!!) bitFirst!!.addAll(bitList!!)
@ -280,6 +282,13 @@ class MMediaMuxer {
mediaMuxer = null mediaMuxer = null
Logd("RELEASE MUXER, path = $outputPath") Logd("RELEASE MUXER, path = $outputPath")
} }
this.outputPath?.let { path ->
this.completion?.invoke(path)
} ?: run {
Logd("no path")
}
} }
private fun getNV21(inputWidth: Int, inputHeight: Int, scaled: Bitmap): ByteArray { private fun getNV21(inputWidth: Int, inputHeight: Int, scaled: Bitmap): ByteArray {

@ -800,5 +800,9 @@
<string name="board">board</string> <string name="board">board</string>
<string name="data_loss_warning">All unsaved changes will be lost. Do you really want to continue?</string> <string name="data_loss_warning">All unsaved changes will be lost. Do you really want to continue?</string>
<string name="replayer">Replayer</string> <string name="replayer">Replayer</string>
<string name="video_available">Video available!</string>
<string name="video_retrieval_message">Your video has been generated at the following path</string>
<string name="open_file_with">Open file with</string>
<string name="video_export_started">Your video is currently exported, we\'ll send you a notification when it\'s available. Expect approximately one minute!</string>
</resources> </resources>

Loading…
Cancel
Save