1package com.peachpasswords.sync23import android.app.Notification4import android.app.NotificationChannel5import android.app.NotificationManager6import android.app.PendingIntent7import android.app.Service8import android.content.Context9import android.content.Intent10import android.os.Build11import android.os.IBinder12import androidx.core.app.NotificationCompat13import com.peachpasswords.MainActivity14import com.peachpasswords.R15import dagger.hilt.android.AndroidEntryPoint16import com.peachpasswords.vault.VaultCrypto17import com.peachpasswords.vault.VaultRepository18import kotlinx.coroutines.CoroutineScope19import kotlinx.coroutines.SupervisorJob20import kotlinx.coroutines.cancel21import javax.inject.Inject22232425262728293031323334353637@AndroidEntryPoint38class PeachSyncService : Service() {3940 @Inject lateinit var listener: SyncReconnectListener41 @Inject lateinit var vault: VaultRepository42 @Inject lateinit var deviceStore: PairedDeviceStore43 @Inject lateinit var pendingVouchedStore: PendingVouchedDeviceStore44 @Inject lateinit var syncAlertStore: SyncAlertStore4546 private val scope = CoroutineScope(SupervisorJob())4748 override fun onBind(intent: Intent?): IBinder? = null4950 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {51 startForeground(NOTIFICATION_ID, buildNotification())52 startReconnectListener()53 54 55 56 return START_STICKY57 }5859 override fun onDestroy() {60 listener.stop()61 scope.cancel()62 super.onDestroy()63 }6465 666768697071 private fun startReconnectListener() {72 val master = vault.syncMasterKey() ?: return73 try {74 val keys = deriveLocalSyncKeypair(master, getOrCreateDeviceId(this))75 val identity = buildLocalIdentity(this, master)76 listener.start(scope, keys.secretKey, keys.publicKey, identity) {77 if (!vault.isUnlocked.value) null else VaultSyncAdapter(78 repo = vault,79 ourDeviceId = getOrCreateDeviceId(this),80 pairedDevices = deviceStore,81 pendingVouched = pendingVouchedStore,82 syncAlerts = syncAlertStore,83 )84 }85 } finally {86 VaultCrypto.zero(master)87 }88 }8990 private fun buildNotification(): Notification {91 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager92 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {93 manager.createNotificationChannel(94 NotificationChannel(CHANNEL_ID, "Peach sync", NotificationManager.IMPORTANCE_LOW).apply {95 description = "Shown while Peach is reachable for syncing on your network."96 setShowBadge(false)97 },98 )99 }100 val open = PendingIntent.getActivity(101 this,102 0,103 Intent(this, MainActivity::class.java),104 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,105 )106 return NotificationCompat.Builder(this, CHANNEL_ID)107 .setContentTitle("Peach sync is active")108 .setContentText("Your other devices can reach this phone on your network.")109 .setSmallIcon(R.drawable.ic_launcher_monochrome)110 .setOngoing(true)111 .setPriority(NotificationCompat.PRIORITY_LOW)112 .setContentIntent(open)113 .build()114 }115116 companion object {117 private const val CHANNEL_ID = "peach_sync"118 private const val NOTIFICATION_ID = 4201119120 fun start(context: Context) {121 val intent = Intent(context, PeachSyncService::class.java)122 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {123 context.startForegroundService(intent)124 } else {125 context.startService(intent)126 }127 }128129 fun stop(context: Context) {130 context.stopService(Intent(context, PeachSyncService::class.java))131 }132 }133}