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:launchMode="singleTop"/>
<service android:name="net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService" android:exported="false"/>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

@ -1,8 +1,13 @@
package net.pokeranalytics.android.ui.modules.handhistory
import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
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.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.extensions.toByteArray
import net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayerAnimator
import net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService
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
class HandHistoryActivity : BaseActivity() {
private var replayExportService: ReplayExportService? = null
enum class IntentKey(val keyName: String) {
IDENTIFIER("identifier"),
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?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hand_history)
initData()
initUI()
}
private fun initData() {
val intent = Intent(this, ReplayExportService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
/**
* Init UI
*/
@ -89,12 +113,6 @@ class HandHistoryActivity : BaseActivity() {
val shouldShowDataLossWarning = ((this.fragment as? HandHistoryFragment)?.isEditing == true)
// this.fragment?.let { hhFragment ->
// if ((hhFragment as? HandHistoryFragment)?.isEditing == true) {
// shouldShowDataLossWarning = true
// }
// }
if (shouldShowDataLossWarning) {
AlertDialog.Builder(this)
@ -142,6 +160,9 @@ class HandHistoryActivity : BaseActivity() {
}
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 ->
if (granted) {
videoExport()
@ -151,34 +172,11 @@ class HandHistoryActivity : BaseActivity() {
private fun videoExport() {
val animator = ReplayerAnimator(this.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}")
}
val handHistoryId = intent.getStringExtra(IntentKey.IDENTIFIER.keyName)
this.replayExportService?.export(handHistoryId) ?: run {
Toast.makeText(this, "Export service not available. Please contact support", Toast.LENGTH_LONG).show()
}
muxer.CreateVideo()
val path = muxer.GetPath()
Timber.d("**** Video path = $path")
}
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.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.graphics.Color
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 {
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 {
@ -63,22 +82,4 @@ class TriggerNotification(context: Context, title: String, body: String) {
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 _title: String? = null
private var _mess: String? = null
private var completion: ((String) -> (Unit))? = null
fun Init(
activity: Activity?,
@ -82,7 +83,8 @@ class MMediaMuxer {
}
}
fun CreateVideo() {
fun CreateVideo(handler: (String) -> (Unit)) {
this.completion = handler
currentIndexFrame = 0
Logd("Prepare Frames Data")
bitFirst!!.addAll(bitList!!)
@ -280,6 +282,13 @@ class MMediaMuxer {
mediaMuxer = null
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 {

@ -800,5 +800,9 @@
<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="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>

Loading…
Cancel
Save