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 return START_STICKY60 }6162 override fun onDestroy() {63 cloudRelayListener.stop()64 scope.cancel()65 super.onDestroy()66 }6768 697071727374 private fun startReconnectListener() {75 val master = vault.syncMasterKey() ?: return76 try {77 val keys = deriveLocalSyncKeypair(master, getOrCreateDeviceId(this))78 val identity = buildLocalIdentity(this, master)79 cloudRelayListener.start(scope, keys.secretKey, keys.publicKey, identity) {80 if (!vault.isUnlocked.value) null else VaultSyncAdapter(81 repo = vault,82 ourDeviceId = getOrCreateDeviceId(this),83 pairedDevices = deviceStore,84 pendingVouched = pendingVouchedStore,85 syncAlerts = syncAlertStore,86 )87 }88 } finally {89 VaultCrypto.zero(master)90 }91 }9293 private fun buildNotification(): Notification {94 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager95 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {96 manager.createNotificationChannel(97 NotificationChannel(CHANNEL_ID, "Peach sync", NotificationManager.IMPORTANCE_LOW).apply {98 description = "Shown while Peach is reachable for encrypted device sync."99 setShowBadge(false)100 },101 )102 }103 val open = PendingIntent.getActivity(104 this,105 0,106 Intent(this, MainActivity::class.java),107 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,108 )109 return NotificationCompat.Builder(this, CHANNEL_ID)110 .setContentTitle("Peach sync is active")111 .setContentText("Your other devices can reach this phone for encrypted sync.")112 .setSmallIcon(R.drawable.ic_launcher_monochrome)113 .setOngoing(true)114 .setPriority(NotificationCompat.PRIORITY_LOW)115 .setContentIntent(open)116 .build()117 }118119 companion object {120 private const val CHANNEL_ID = "peach_sync"121 private const val NOTIFICATION_ID = 4201122123 fun start(context: Context) {124 val intent = Intent(context, PeachSyncService::class.java)125 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {126 context.startForegroundService(intent)127 } else {128 context.startService(intent)129 }130 }131132 fun stop(context: Context) {133 context.stopService(Intent(context, PeachSyncService::class.java))134 }135 }136}