aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/util/services
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2022-07-15 00:53:48 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2022-07-15 01:13:47 +0530
commit549ee790d3e52bc62565ddf92e6a556e98b5195e (patch)
treed5758e5eb80093704e683c8da926838e18182588 /app/src/main/java/dev/msfjarvis/aps/util/services
parent010c6e227c9cc27f4d01bc912311f977b2aeb3a7 (diff)
downloadHardenedPass-549ee790d3e52bc62565ddf92e6a556e98b5195e.tar.gz
HardenedPass-549ee790d3e52bc62565ddf92e6a556e98b5195e.zip
all: re-do package structure yet again
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/util/services')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt206
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt172
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt159
3 files changed, 0 insertions, 537 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt
deleted file mode 100644
index 34c3a989..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.util.services
-
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.app.Service
-import android.content.ClipData
-import android.content.Intent
-import android.os.Build
-import android.os.IBinder
-import androidx.annotation.RequiresApi
-import androidx.core.app.NotificationCompat
-import androidx.core.content.getSystemService
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.util.extensions.clipboard
-import dev.msfjarvis.aps.util.extensions.sharedPrefs
-import dev.msfjarvis.aps.util.settings.PreferenceKeys
-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 logcat.logcat
-
-class ClipboardService : Service() {
-
- private val scope = CoroutineScope(Job() + Dispatchers.Main)
-
- 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 = intent.getIntExtra(EXTRA_NOTIFICATION_TIME, 45)
-
- if (time == 0) {
- stopSelf()
- }
-
- createNotification(time)
- scope.launch {
- withContext(Dispatchers.IO) { startTimer(time) }
- withContext(Dispatchers.Main) {
- 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 = sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_HISTORY, false)
- val clipboard = clipboard
-
- if (clipboard != null) {
- scope.launch {
- logcat { "Clearing the clipboard" }
- val clip = ClipData.newPlainText("pgp_handler_result_pm", "")
- clipboard.setPrimaryClip(clip)
- if (deepClear) {
- withContext(Dispatchers.IO) {
- repeat(CLIPBOARD_CLEAR_COUNT) {
- val count = (it * 500).toString()
- clipboard.setPrimaryClip(ClipData.newPlainText(count, count))
- }
- }
- }
- }
- } else {
- logcat { "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 createNotification(clearTime: Int) {
- val clearTimeMs = clearTime * 1000L
- val clearIntent = Intent(this, ClipboardService::class.java).apply { action = ACTION_CLEAR }
- val pendingIntent =
- if (Build.VERSION.SDK_INT >= 26) {
- PendingIntent.getForegroundService(
- this,
- 0,
- clearIntent,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- PendingIntent.FLAG_UPDATE_CURRENT
- }
- )
- } else {
- PendingIntent.getService(
- this,
- 0,
- clearIntent,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- PendingIntent.FLAG_UPDATE_CURRENT
- },
- )
- }
- val notification =
- if (Build.VERSION.SDK_INT <= 23) {
- createNotificationApi23(pendingIntent)
- } else {
- createNotificationApi24(pendingIntent, clearTimeMs)
- }
-
- createNotificationChannel()
- startForeground(1, notification)
- }
-
- private fun createNotificationApi23(pendingIntent: PendingIntent): Notification {
- return NotificationCompat.Builder(this, CHANNEL_ID)
- .setContentTitle(getString(R.string.app_name))
- .setContentText(getString(R.string.tap_clear_clipboard))
- .setSmallIcon(R.drawable.ic_action_secure_24dp)
- .setContentIntent(pendingIntent)
- .setUsesChronometer(true)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .build()
- }
-
- @RequiresApi(24)
- private fun createNotificationApi24(
- pendingIntent: PendingIntent,
- clearTimeMs: Long
- ): Notification {
- return NotificationCompat.Builder(this, CHANNEL_ID)
- .setContentTitle(getString(R.string.app_name))
- .setContentText(getString(R.string.tap_clear_clipboard))
- .setSmallIcon(R.drawable.ic_action_secure_24dp)
- .setContentIntent(pendingIntent)
- .setUsesChronometer(true)
- .setChronometerCountDown(true)
- .setShowWhen(true)
- .setWhen(System.currentTimeMillis() + clearTimeMs)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .build()
- }
-
- private fun createNotificationChannel() {
- if (Build.VERSION.SDK_INT >= 26) {
- val serviceChannel =
- NotificationChannel(
- CHANNEL_ID,
- getString(R.string.app_name),
- NotificationManager.IMPORTANCE_LOW
- )
- val manager = getSystemService<NotificationManager>()
- if (manager != null) {
- manager.createNotificationChannel(serviceChannel)
- } else {
- logcat { "Failed to create notification channel" }
- }
- }
- }
-
- companion object {
-
- const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
- const val EXTRA_NOTIFICATION_TIME = "EXTRA_NOTIFICATION_TIME"
- private const val ACTION_CLEAR = "ACTION_CLEAR_CLIPBOARD"
- private const val CHANNEL_ID = "NotificationService"
- // Newest Samsung phones now feature a history of up to 30 items. To err on the side of
- // caution,
- // push 35 fake ones.
- private const val CLIPBOARD_CLEAR_COUNT = 35
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt
deleted file mode 100644
index 3a1c69d3..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.util.services
-
-import android.content.Context
-import android.os.Build
-import android.os.CancellationSignal
-import android.service.autofill.AutofillService
-import android.service.autofill.FillCallback
-import android.service.autofill.FillRequest
-import android.service.autofill.FillResponse
-import android.service.autofill.SaveCallback
-import android.service.autofill.SaveRequest
-import androidx.annotation.RequiresApi
-import com.github.androidpasswordstore.autofillparser.AutofillScenario
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.github.androidpasswordstore.autofillparser.FillableForm
-import com.github.androidpasswordstore.autofillparser.FixedSaveCallback
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import com.github.androidpasswordstore.autofillparser.cachePublicSuffixList
-import com.github.androidpasswordstore.autofillparser.passwordValue
-import com.github.androidpasswordstore.autofillparser.recoverNodes
-import com.github.androidpasswordstore.autofillparser.usernameValue
-import dagger.hilt.android.AndroidEntryPoint
-import dev.msfjarvis.aps.BuildConfig
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.ui.autofill.AutofillSaveActivity
-import dev.msfjarvis.aps.util.autofill.Api30AutofillResponseBuilder
-import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
-import dev.msfjarvis.aps.util.extensions.getString
-import dev.msfjarvis.aps.util.extensions.hasFlag
-import dev.msfjarvis.aps.util.extensions.sharedPrefs
-import dev.msfjarvis.aps.util.settings.PreferenceKeys
-import javax.inject.Inject
-import logcat.LogPriority.ERROR
-import logcat.logcat
-
-@RequiresApi(26)
-@AndroidEntryPoint
-class OreoAutofillService : AutofillService() {
-
- companion object {
-
- // TODO: Provide a user-configurable denylist
- private val DENYLISTED_PACKAGES =
- listOf(
- BuildConfig.APPLICATION_ID,
- "android",
- "com.android.settings",
- "com.android.settings.intelligence",
- "com.android.systemui",
- "com.oneplus.applocker",
- "org.sufficientlysecure.keychain",
- )
-
- private const val DISABLE_AUTOFILL_DURATION_MS = 1000 * 60 * 60 * 24L
- }
-
- @Inject lateinit var api30ResponseBuilderFactory: Api30AutofillResponseBuilder.Factory
- @Inject lateinit var responseBuilderFactory: AutofillResponseBuilder.Factory
-
- override fun onCreate() {
- super.onCreate()
- cachePublicSuffixList(applicationContext)
- }
-
- override fun onFillRequest(
- request: FillRequest,
- cancellationSignal: CancellationSignal,
- callback: FillCallback
- ) {
- val structure =
- request.fillContexts.lastOrNull()?.structure
- ?: run {
- callback.onSuccess(null)
- return
- }
- if (structure.activityComponent.packageName in DENYLISTED_PACKAGES) {
- if (Build.VERSION.SDK_INT >= 28) {
- callback.onSuccess(
- FillResponse.Builder().run {
- disableAutofill(DISABLE_AUTOFILL_DURATION_MS)
- build()
- }
- )
- } else {
- callback.onSuccess(null)
- }
- return
- }
- val formToFill =
- FillableForm.parseAssistStructure(
- this,
- structure,
- isManualRequest = request.flags hasFlag FillRequest.FLAG_MANUAL_REQUEST,
- getCustomSuffixes(),
- )
- ?: run {
- logcat { "Form cannot be filled" }
- callback.onSuccess(null)
- return
- }
- if (Build.VERSION.SDK_INT >= 30) {
- api30ResponseBuilderFactory
- .create(formToFill)
- .fillCredentials(this, request.inlineSuggestionsRequest, callback)
- } else {
- responseBuilderFactory.create(formToFill).fillCredentials(this, callback)
- }
- }
-
- override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
- // SaveCallback's behavior and feature set differs based on both target and device SDK, so
- // we replace it with a wrapper that works the same in all situations.
- @Suppress("NAME_SHADOWING") val callback = FixedSaveCallback(this, callback)
- val structure =
- request.fillContexts.lastOrNull()?.structure
- ?: run {
- callback.onFailure(getString(R.string.oreo_autofill_save_app_not_supported))
- return
- }
- val clientState =
- request.clientState
- ?: run {
- logcat(ERROR) { "Received save request without client state" }
- callback.onFailure(getString(R.string.oreo_autofill_save_internal_error))
- return
- }
- val scenario =
- AutofillScenario.fromClientState(clientState)?.recoverNodes(structure)
- ?: run {
- logcat(ERROR) { "Failed to recover client state or nodes from client state" }
- callback.onFailure(getString(R.string.oreo_autofill_save_internal_error))
- return
- }
- val formOrigin =
- FormOrigin.fromBundle(clientState)
- ?: run {
- logcat(ERROR) { "Failed to recover form origin from client state" }
- callback.onFailure(getString(R.string.oreo_autofill_save_internal_error))
- return
- }
-
- val username = scenario.usernameValue
- val password =
- scenario.passwordValue
- ?: run {
- callback.onFailure(getString(R.string.oreo_autofill_save_passwords_dont_match))
- return
- }
- callback.onSuccess(
- AutofillSaveActivity.makeSaveIntentSender(
- this,
- credentials = Credentials(username, password, null),
- formOrigin = formOrigin
- )
- )
- }
-}
-
-fun Context.getDefaultUsername() =
- sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME)
-
-fun Context.getCustomSuffixes(): Sequence<String> {
- return sharedPrefs
- .getString(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES)
- ?.splitToSequence('\n')
- ?.filter { it.isNotBlank() && it.first() != '.' && it.last() != '.' }
- ?: emptySequence()
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt
deleted file mode 100644
index 91a7ac5f..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package dev.msfjarvis.aps.util.services
-
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.Service
-import android.content.Intent
-import android.net.Uri
-import android.os.Build
-import android.os.IBinder
-import androidx.core.app.NotificationCompat
-import androidx.core.content.getSystemService
-import androidx.documentfile.provider.DocumentFile
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.data.repo.PasswordRepository
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-import java.util.Calendar
-import java.util.TimeZone
-import logcat.logcat
-
-class PasswordExportService : Service() {
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- if (intent != null) {
- when (intent.action) {
- ACTION_EXPORT_PASSWORD -> {
- val uri = intent.getParcelableExtra<Uri>("uri")
- if (uri != null) {
- val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri)
-
- if (targetDirectory != null) {
- createNotification()
- exportPasswords(targetDirectory)
- stopSelf()
- return START_NOT_STICKY
- }
- }
- }
- }
- }
- return super.onStartCommand(intent, flags, startId)
- }
-
- override fun onBind(intent: Intent?): IBinder? {
- return null
- }
-
- /**
- * Exports passwords to the given directory.
- *
- * Recursively copies the existing password store to an external directory.
- *
- * @param targetDirectory directory to copy password directory to.
- */
- private fun exportPasswords(targetDirectory: DocumentFile) {
-
- val repositoryDirectory = requireNotNull(PasswordRepository.getRepositoryDirectory())
- val sourcePassDir = DocumentFile.fromFile(repositoryDirectory)
-
- logcat { "Copying ${repositoryDirectory.path} to $targetDirectory" }
-
- val dateString =
- if (Build.VERSION.SDK_INT >= 26) {
- LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
- } else {
- String.format("%tFT%<tRZ", Calendar.getInstance(TimeZone.getTimeZone("Z")))
- }
-
- val passDir = targetDirectory.createDirectory("password_store_$dateString")
-
- if (passDir != null) {
- copyDirToDir(sourcePassDir, passDir)
- }
- }
-
- /**
- * Copies a password file to a given directory.
- *
- * Note: this does not preserve last modified time.
- *
- * @param passwordFile password file to copy.
- * @param targetDirectory target directory to copy password.
- */
- private fun copyFileToDir(passwordFile: DocumentFile, targetDirectory: DocumentFile) {
- val sourceInputStream = contentResolver.openInputStream(passwordFile.uri)
- val name = passwordFile.name
- val targetPasswordFile = targetDirectory.createFile("application/octet-stream", name!!)
- if (targetPasswordFile?.exists() == true) {
- val destOutputStream = contentResolver.openOutputStream(targetPasswordFile.uri)
-
- if (destOutputStream != null && sourceInputStream != null) {
- sourceInputStream.copyTo(destOutputStream, 1024)
-
- sourceInputStream.close()
- destOutputStream.close()
- }
- }
- }
-
- /**
- * Recursively copies a directory to a destination.
- *
- * @param sourceDirectory directory to copy from.
- * @param targetDirectory directory to copy to.
- */
- private fun copyDirToDir(sourceDirectory: DocumentFile, targetDirectory: DocumentFile) {
- sourceDirectory.listFiles().forEach { file ->
- if (file.isDirectory) {
- // Create new directory and recurse
- val newDir = targetDirectory.createDirectory(file.name!!)
- copyDirToDir(file, newDir!!)
- } else {
- copyFileToDir(file, targetDirectory)
- }
- }
- }
-
- private fun createNotification() {
- createNotificationChannel()
-
- val notification =
- NotificationCompat.Builder(this, CHANNEL_ID)
- .setContentTitle(getString(R.string.app_name))
- .setContentText(getString(R.string.exporting_passwords))
- .setSmallIcon(R.drawable.ic_round_import_export)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .build()
-
- startForeground(2, notification)
- }
-
- private fun createNotificationChannel() {
- if (Build.VERSION.SDK_INT >= 26) {
- val serviceChannel =
- NotificationChannel(
- CHANNEL_ID,
- getString(R.string.app_name),
- NotificationManager.IMPORTANCE_LOW
- )
- val manager = getSystemService<NotificationManager>()
- if (manager != null) {
- manager.createNotificationChannel(serviceChannel)
- } else {
- logcat { "Failed to create notification channel" }
- }
- }
- }
-
- companion object {
-
- const val ACTION_EXPORT_PASSWORD = "ACTION_EXPORT_PASSWORD"
- private const val CHANNEL_ID = "NotificationService"
- }
-}