aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2020-03-05 21:05:50 +0530
committerGitHub <noreply@github.com>2020-03-05 21:05:50 +0530
commit73058d10a8e28fe4e7636ad5c73eeb8651369cdb (patch)
treef5d15b6dc45eb3d2ac5f2e33a5e4362e4b84285d /app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
parentaddefdc9a3eac4124d304ee4f810daaeed419996 (diff)
downloadHardenedPass-73058d10a8e28fe4e7636ad5c73eeb8651369cdb.tar.gz
HardenedPass-73058d10a8e28fe4e7636ad5c73eeb8651369cdb.zip
Resolve various memory leaks (#637)
This migrates the clipboard clear logic into a foreground service that allows us to also provide a notification that runs the clear task immediately on click, rather than wait for the timeout. Co-authored-by: Aditya Wasan <adityawasan55@gmail.com> Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt156
1 files changed, 156 insertions, 0 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
new file mode 100644
index 00000000..c47444ba
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.ClipboardManager
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import androidx.core.content.getSystemService
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import androidx.preference.PreferenceManager
+import com.zeapo.pwdstore.utils.ClipboardUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+
+class ClipboardService : Service() {
+
+ private val scope = CoroutineScope(Job() + Dispatchers.Main)
+ private val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ if (intent != null) {
+ when (intent.action) {
+ ACTION_CLEAR -> {
+ clearClipboard()
+ stopForeground(true)
+ stopSelf()
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ ACTION_START -> {
+ val time = try {
+ Integer.parseInt(settings.getString("general_show_time", "45") as String)
+ } catch (e: NumberFormatException) {
+ 45
+ }
+
+ if (time == 0) {
+ stopSelf()
+ }
+
+ createNotification()
+ scope.launch {
+ withContext(Dispatchers.IO) {
+ startTimer(time)
+ }
+ withContext(Dispatchers.Main) {
+ emitBroadcast()
+ clearClipboard()
+ stopForeground(true)
+ stopSelf()
+ }
+ }
+ return START_NOT_STICKY
+ }
+ }
+ }
+
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ override fun onDestroy() {
+ scope.cancel()
+ super.onDestroy()
+ }
+
+ private fun clearClipboard() {
+ val deepClear = settings.getBoolean("clear_clipboard_20x", false)
+ val clipboardManager = getSystemService<ClipboardManager>()
+
+ if (clipboardManager is ClipboardManager) {
+ scope.launch {
+ ClipboardUtils.clearClipboard(clipboardManager, deepClear)
+ }
+ } else {
+ Timber.tag("ClipboardService").d("Cannot get clipboard manager service")
+ }
+ }
+
+ private suspend fun startTimer(showTime: Int) {
+ var current = 0
+ while (scope.isActive && current < showTime) {
+ // Block for 1s or until cancel is signalled
+ current++
+ delay(1000)
+ }
+ }
+
+ private fun emitBroadcast() {
+ val localBroadcastManager = LocalBroadcastManager.getInstance(this)
+ val clearIntent = Intent(ACTION_CLEAR)
+ localBroadcastManager.sendBroadcast(clearIntent)
+ }
+
+ private fun createNotification() {
+ createNotificationChannel()
+ val clearIntent = Intent(this, ClipboardService::class.java)
+ clearIntent.action = ACTION_CLEAR
+ val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ PendingIntent.getForegroundService(this, 0, clearIntent, PendingIntent.FLAG_UPDATE_CURRENT)
+ } else {
+ PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
+ val notification = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText(getString(R.string.tap_clear_clipboard))
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setContentIntent(pendingIntent)
+ .setUsesChronometer(true)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build()
+
+ startForeground(1, notification)
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val serviceChannel = NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.app_name),
+ NotificationManager.IMPORTANCE_LOW
+ )
+ val manager = getSystemService<NotificationManager>()
+ if (manager != null) {
+ manager.createNotificationChannel(serviceChannel)
+ } else {
+ Timber.tag("ClipboardService").d("Failed to create notification channel")
+ }
+ }
+ }
+ companion object {
+ private const val ACTION_CLEAR = "ACTION_CLEAR_CLIPBOARD"
+ private const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
+ private const val CHANNEL_ID = "NotificationService"
+ }
+}