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 cloudRelayListener: CloudRelayReconnectListener42 @Inject lateinit var vault: VaultRepository43 @Inject lateinit var deviceStore: PairedDeviceStore44 @Inject lateinit var pendingVouchedStore: PendingVouchedDeviceStore45 @Inject lateinit var syncAlertStore: SyncAlertStore4647 private val scope = CoroutineScope(SupervisorJob())4849 override fun onBind(intent: Intent?): IBinder? = null5051 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {52 startForeground(NOTIFICATION_ID, buildNotification())53 startReconnectListener()54 55 56 57 return START_STICKY58 }5960 override fun onDestroy() {61 listener.stop()62 cloudRelayListener.stop()63 scope.cancel()64 super.onDestroy()65 }6667 686970717273 private fun startReconnectListener() {74 val master = vault.syncMasterKey() ?: return75 try {76 val keys = deriveLocalSyncKeypair(master, getOrCreateDeviceId(this))77 val identity = buildLocalIdentity(this, master)78 listener.start(scope, keys.secretKey, keys.publicKey, identity) {79 if (!vault.isUnlocked.value) null else VaultSyncAdapter(80 repo = vault,81 ourDeviceId = getOrCreateDeviceId(this),82 pairedDevices = deviceStore,83 pendingVouched = pendingVouchedStore,84 syncAlerts = syncAlertStore,85 )86 }87 cloudRelayListener.start(scope, keys.secretKey, keys.publicKey, identity) {88 if (!vault.isUnlocked.value) null else VaultSyncAdapter(89 repo = vault,90 ourDeviceId = getOrCreateDeviceId(this),91 pairedDevices = deviceStore,92 pendingVouched = pendingVouchedStore,93 syncAlerts = syncAlertStore,94 )95 }96 } finally {97 VaultCrypto.zero(master)98 }99 }100101 private fun buildNotification(): Notification {102 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager103 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {104 manager.createNotificationChannel(105 NotificationChannel(CHANNEL_ID, "Peach sync", NotificationManager.IMPORTANCE_LOW).apply {106 description = "Shown while Peach is reachable for encrypted device sync."107 setShowBadge(false)108 },109 )110 }111 val open = PendingIntent.getActivity(112 this,113 0,114 Intent(this, MainActivity::class.java),115 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,116 )117 return NotificationCompat.Builder(this, CHANNEL_ID)118 .setContentTitle("Peach sync is active")119 .setContentText("Your other devices can reach this phone for encrypted sync.")120 .setSmallIcon(R.drawable.ic_launcher_monochrome)121 .setOngoing(true)122 .setPriority(NotificationCompat.PRIORITY_LOW)123 .setContentIntent(open)124 .build()125 }126127 companion object {128 private const val CHANNEL_ID = "peach_sync"129 private const val NOTIFICATION_ID = 4201130131 fun start(context: Context) {132 val intent = Intent(context, PeachSyncService::class.java)133 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {134 context.startForegroundService(intent)135 } else {136 context.startService(intent)137 }138 }139140 fun stop(context: Context) {141 context.stopService(Intent(context, PeachSyncService::class.java))142 }143 }144}