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 747576777879 private fun startReconnectListener() {80 val master = vault.syncMasterKey() ?: return81 try {82 val keys = deriveLocalSyncKeypair(master, getOrCreateDeviceId(this))83 val identity = buildLocalIdentity(this, master)84 cloudRelayListener.start(scope, keys.secretKey, keys.publicKey, identity) {85 if (!vault.isUnlocked.value) null else VaultSyncAdapter(86 repo = vault,87 ourDeviceId = getOrCreateDeviceId(this),88 pairedDevices = deviceStore,89 pendingVouched = pendingVouchedStore,90 syncAlerts = syncAlertStore,91 )92 }93 } finally {94 VaultCrypto.zero(master)95 }96 }9798 private fun buildNotification(): Notification {99 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager100 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {101 manager.createNotificationChannel(102 NotificationChannel(CHANNEL_ID, "Peach sync", NotificationManager.IMPORTANCE_LOW).apply {103 description = "Shown while Peach is reachable for encrypted device sync."104 setShowBadge(false)105 },106 )107 }108 val open = PendingIntent.getActivity(109 this,110 0,111 Intent(this, MainActivity::class.java),112 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,113 )114 return NotificationCompat.Builder(this, CHANNEL_ID)115 .setContentTitle("Peach sync is active")116 .setContentText("Your other devices can reach this phone for encrypted sync.")117 .setSmallIcon(R.drawable.ic_launcher_monochrome)118 .setOngoing(true)119 .setPriority(NotificationCompat.PRIORITY_LOW)120 .setContentIntent(open)121 .build()122 }123124 companion object {125 private const val CHANNEL_ID = "peach_sync"126 private const val NOTIFICATION_ID = 4201127128 fun start(context: Context) {129 val intent = Intent(context, PeachSyncService::class.java)130 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {131 context.startForegroundService(intent)132 } else {133 context.startService(intent)134 }135 }136137 fun stop(context: Context) {138 context.stopService(Intent(context, PeachSyncService::class.java))139 }140 }141}