package com.massive import android.annotation.SuppressLint import android.app.* import android.content.ActivityNotFoundException import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.os.Build import android.os.CountDownTimer import android.os.PowerManager import android.provider.Settings import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import com.facebook.react.bridge.Callback import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import kotlin.math.floor class AlarmModule constructor(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) { var countdownTimer: CountDownTimer? = null override fun getName(): String { return "AlarmModule" } private val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { Log.d("AlarmModule", "Received broadcast intent") Toast.makeText(reactApplicationContext, "called from test receiver", Toast.LENGTH_SHORT) .show() } } @RequiresApi(api = Build.VERSION_CODES.O) @ReactMethod fun add(milliseconds: Int, vibrate: Boolean, sound: String?) { Log.d("AlarmModule", "Add 1 min to alarm.") val addIntent = Intent(reactApplicationContext, TimerService::class.java) addIntent.action = "add" addIntent.putExtra("vibrate", vibrate) addIntent.putExtra("sound", sound) addIntent.data = Uri.parse("$milliseconds") reactApplicationContext.startService(addIntent) } @RequiresApi(api = Build.VERSION_CODES.O) @ReactMethod fun stop() { Log.d("AlarmModule", "Stop alarm.") val timerIntent = Intent(reactApplicationContext, TimerService::class.java) reactApplicationContext.stopService(timerIntent) val alarmIntent = Intent(reactApplicationContext, AlarmService::class.java) reactApplicationContext.stopService(alarmIntent) } @RequiresApi(api = Build.VERSION_CODES.O) @ReactMethod fun timer(milliseconds: Int, vibrate: Boolean, sound: String?, noSound: Boolean = false) { Log.d("AlarmModule", "Queue alarm for $milliseconds delay") val manager = getManager() manager.cancel(NOTIFICATION_ID_DONE) reactApplicationContext.stopService( Intent( reactApplicationContext, AlarmService::class.java ) ) countdownTimer?.cancel() countdownTimer = getTimer(milliseconds, vibrate, sound) countdownTimer?.start() } @RequiresApi(Build.VERSION_CODES.M) @ReactMethod fun ignoringBattery(callback: Callback) { val packageName = reactApplicationContext.packageName val pm = reactApplicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { callback.invoke(pm.isIgnoringBatteryOptimizations(packageName)) } else { callback.invoke(true) } } @SuppressLint("BatteryLife") @RequiresApi(Build.VERSION_CODES.M) @ReactMethod fun ignoreBattery() { val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) intent.data = Uri.parse("package:" + reactApplicationContext.packageName) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK try { reactApplicationContext.startActivity(intent) } catch (e: ActivityNotFoundException) { Toast.makeText( reactApplicationContext, "Requests to ignore battery optimizations are disabled on your device.", Toast.LENGTH_LONG ).show() } } @RequiresApi(Build.VERSION_CODES.M) private fun getTimer(endMs: Int, vibrate: Boolean, sound: String?): CountDownTimer { val builder = getBuilder() return object : CountDownTimer(endMs.toLong(), 1000) { @RequiresApi(Build.VERSION_CODES.O) override fun onTick(current: Long) { val seconds = floor((current / 1000).toDouble() % 60).toInt().toString().padStart(2, '0') val minutes = floor((current / 1000).toDouble() / 60).toInt().toString().padStart(2, '0') builder.setContentText("$minutes:$seconds").setAutoCancel(false).setDefaults(0) .setProgress(endMs, current.toInt(), false) .setCategory(NotificationCompat.CATEGORY_PROGRESS).priority = NotificationCompat.PRIORITY_LOW val manager = getManager() manager.notify(NOTIFICATION_ID_PENDING, builder.build()) } @RequiresApi(Build.VERSION_CODES.O) override fun onFinish() { val context = reactApplicationContext val finishIntent = Intent(context, StopAlarm::class.java) val finishPending = PendingIntent.getActivity( context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE ) val fullIntent = Intent(context, TimerDone::class.java) val fullPending = PendingIntent.getActivity( context, 0, fullIntent, PendingIntent.FLAG_IMMUTABLE ) builder.setContentText("Timer finished.").setProgress(0, 0, false) .setAutoCancel(true).setOngoing(true).setFullScreenIntent(fullPending, true) .setContentIntent(finishPending).setChannelId(CHANNEL_ID_DONE) .setCategory(NotificationCompat.CATEGORY_ALARM).priority = NotificationCompat.PRIORITY_HIGH val manager = getManager() manager.notify(NOTIFICATION_ID_DONE, builder.build()) manager.cancel(NOTIFICATION_ID_PENDING) val alarmIntent = Intent(context, AlarmService::class.java) alarmIntent.putExtra("vibrate", vibrate) alarmIntent.putExtra("sound", sound) context.startService(alarmIntent) } } } @SuppressLint("UnspecifiedImmutableFlag") @RequiresApi(Build.VERSION_CODES.M) private fun getBuilder(): NotificationCompat.Builder { val context = reactApplicationContext val contentIntent = Intent(context, MainActivity::class.java) val pendingContent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE) reactApplicationContext.registerReceiver(receiver, IntentFilter(STOP_BROADCAST)) val stopIntent = Intent(STOP_BROADCAST) stopIntent.flags =Intent.FLAG_ACTIVITY_NEW_TASK val pendingStop = PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_MUTABLE) return NotificationCompat.Builder(context, CHANNEL_ID_PENDING) .setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting") .setContentIntent(pendingContent) .addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop) .setDeleteIntent(pendingStop) } @RequiresApi(Build.VERSION_CODES.O) fun getManager(): NotificationManager { val alarmsChannel = NotificationChannel( CHANNEL_ID_DONE, CHANNEL_ID_DONE, NotificationManager.IMPORTANCE_HIGH ) alarmsChannel.description = "Alarms for rest timers." alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC val notificationManager = reactApplicationContext.getSystemService( NotificationManager::class.java ) notificationManager.createNotificationChannel(alarmsChannel) val timersChannel = NotificationChannel( CHANNEL_ID_PENDING, CHANNEL_ID_PENDING, NotificationManager.IMPORTANCE_LOW ) timersChannel.setSound(null, null) timersChannel.description = "Progress on rest timers." notificationManager.createNotificationChannel(timersChannel) return notificationManager } companion object { const val STOP_BROADCAST = "stop-timer-event" const val CHANNEL_ID_PENDING = "Timer" const val CHANNEL_ID_DONE = "Alarm" const val NOTIFICATION_ID_PENDING = 1 const val NOTIFICATION_ID_DONE = 2 } }