diff options
| author | Harsh Shandilya <msfjarvis@gmail.com> | 2020-03-05 21:05:50 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-05 21:05:50 +0530 |
| commit | 73058d10a8e28fe4e7636ad5c73eeb8651369cdb (patch) | |
| tree | f5d15b6dc45eb3d2ac5f2e33a5e4362e4b84285d /app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt | |
| parent | addefdc9a3eac4124d304ee4f810daaeed419996 (diff) | |
| download | HardenedPass-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.kt | 156 |
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" + } +} |
