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.Inject22232425262728293031323334353637383940@AndroidEntryPoint41class PeachSyncService : Service() {4243 @Inject lateinit var cloudRelayListener: CloudRelayReconnectListener44 @Inject lateinit var vault: VaultRepository45 @Inject lateinit var deviceStore: PairedDeviceStore46 @Inject lateinit var pendingVouchedStore: PendingVouchedDeviceStore47 @Inject lateinit var syncAlertStore: SyncAlertStore4849 private val scope = CoroutineScope(SupervisorJob())5051 override fun onBind(intent: Intent?): IBinder? = null5253 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {54 startForeground(NOTIFICATION_ID, buildNotification())55 startReconnectListener()56 57 58 59 60 cloudRelayListener.requestReconcile()61 62 63 64 return START_STICKY65 }6667 override fun onDestroy() {68 cloudRelayListener.stop()69 scope.cancel()70 super.onDestroy()71 }7273 74757677787980818283848586 private fun startReconnectListener() {87 val master = vault.syncMasterKey() ?: return88 try {89 val keys = deriveLocalSyncKeypair(master, getOrCreateDeviceId(this))90 val identity = buildLocalIdentity(this, master)91 cloudRelayListener.start(scope, keys.secretKey, keys.publicKey, identity) {92 if (!vault.isUnlocked.value) null else VaultSyncAdapter(93 repo = vault,94 ourDeviceId = getOrCreateDeviceId(this),95 pairedDevices = deviceStore,96 pendingVouched = pendingVouchedStore,97 syncAlerts = syncAlertStore,98 )99 }100 } finally {101 VaultCrypto.zero(master)102 }103 }104105 private fun buildNotification(): Notification {106 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager107 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {108 manager.createNotificationChannel(109 NotificationChannel(CHANNEL_ID, "Peach sync", NotificationManager.IMPORTANCE_LOW).apply {110 description = "Shown while Peach is reachable for encrypted device sync."111 setShowBadge(false)112 },113 )114 }115 val open = PendingIntent.getActivity(116 this,117 0,118 Intent(this, MainActivity::class.java),119 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,120 )121 return NotificationCompat.Builder(this, CHANNEL_ID)122 .setContentTitle("Peach sync is active")123 .setContentText("Your other devices can reach this phone for encrypted sync.")124 .setSmallIcon(R.drawable.ic_launcher_monochrome)125 .setOngoing(true)126 .setPriority(NotificationCompat.PRIORITY_LOW)127 .setContentIntent(open)128 .build()129 }130131 companion object {132 private const val CHANNEL_ID = "peach_sync"133 private const val NOTIFICATION_ID = 4201134135 fun start(context: Context) {136 val intent = Intent(context, PeachSyncService::class.java)137 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {138 context.startForegroundService(intent)139 } else {140 context.startService(intent)141 }142 }143144 fun stop(context: Context) {145 context.stopService(Intent(context, PeachSyncService::class.java))146 }147 }148}