Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,35 @@ android {
versionCode = project.findProperty("app.versionCode").toString().toInt()
versionName = project.findProperty("app.versionName").toString()
resourceConfigurations += listOf(
"en", "ca", "cs", "da", "de", "el", "es", "fr", "hu", "in", "it", "ja", "my", "nb", "nl", "nn", "pl", "pt-rBR", "pt-rPT", "ro", "ru", "sk", "sv", "tl", "tr", "uk", "vi", "zh-rCN", "zh-rTW"
"en",
"ca",
"cs",
"da",
"de",
"el",
"es",
"fr",
"hu",
"in",
"it",
"ja",
"my",
"nb",
"nl",
"nn",
"pl",
"pt-rBR",
"pt-rPT",
"ro",
"ru",
"sk",
"sv",
"tl",
"tr",
"uk",
"vi",
"zh-rCN",
"zh-rTW"
)
vectorDrawables.generatedDensities?.clear()
ndk {
Expand Down Expand Up @@ -64,7 +92,7 @@ android {
)
signingConfig = signingConfigs.getByName("release")
}
create("beta"){
create("beta") {
initWith(getByName("release"))
applicationIdSuffix = ".beta"
versionNameSuffix = "-BETA"
Expand All @@ -82,10 +110,15 @@ android {
if (buildType.isMinifyEnabled) {
// Function to copy proguard mapping.txt file
fun copyMapping(suffix: String) {
val mappingFile = layout.buildDirectory.file("outputs/mapping/${buildType.name}/mapping.txt").get().asFile
val mappingFile =
layout.buildDirectory.file("outputs/mapping/${buildType.name}/mapping.txt")
.get().asFile
if (mappingFile.exists()) {
val target =
File(project.projectDir, "obfuscation/mapping-${buildType.name}-$suffix.txt")
File(
project.projectDir,
"obfuscation/mapping-${buildType.name}-$suffix.txt"
)
target.parentFile.mkdirs()
mappingFile.copyTo(target, overwrite = true)
println("Copied mapping to: ${target.absolutePath}")
Expand All @@ -100,7 +133,7 @@ android {
tasks.matching { it.name == "bundle${name.capitalize()}" }
.forEach { bundleTask ->
bundleTask.doLast {
copyMapping( "bundle")
copyMapping("bundle")
}
}

Expand All @@ -118,13 +151,17 @@ android {
return@doLast
}
// Target zip file
val outputZip = File(project.projectDir, "obfuscation/${buildType.name}-debug-symbols.zip")
val outputZip = File(
project.projectDir,
"obfuscation/${buildType.name}-debug-symbols.zip"
)
outputZip.parentFile.mkdirs()
ZipOutputStream(outputZip.outputStream()).use { zipOut ->
nativeLibsDir.walkTopDown().forEach { file ->
if (file.isFile) {
// Preserve "lib/ABI/..." folder structure in the zip
val relativePath = nativeLibsDir.toPath().relativize(file.toPath()).toString()
val relativePath =
nativeLibsDir.toPath().relativize(file.toPath()).toString()
zipOut.putNextEntry(ZipEntry(relativePath))
file.inputStream().use { it.copyTo(zipOut) }
zipOut.closeEntry()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.io.Serializable
enum class Folder : Serializable {
NOTES,
DELETED,
HIDDEN,
ARCHIVED;

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ fun Folder.movedToResId(): Int {
Folder.DELETED -> R.plurals.deleted_selected_notes
Folder.ARCHIVED -> R.plurals.archived_selected_notes
Folder.NOTES -> R.plurals.restored_selected_notes
Folder.HIDDEN -> R.plurals.hidden_selected_notes
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,13 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
add(2, R.id.Archived, CATEGORY_SYSTEM + 2, R.string.archived)
.setCheckable(true)
.setIcon(R.drawable.archive)
add(3, R.id.Reminders, CATEGORY_SYSTEM + 3, R.string.reminders)
add(2, R.id.Hidden, CATEGORY_SYSTEM + 3, R.string.hiddens)
.setCheckable(true)
.setIcon(R.drawable.hidden)
add(3, R.id.Reminders, CATEGORY_SYSTEM + 4, R.string.reminders)
.setCheckable(true)
.setIcon(R.drawable.notifications)
add(3, R.id.Settings, CATEGORY_SYSTEM + 4, R.string.settings)
add(4, R.id.Settings, CATEGORY_SYSTEM + 5, R.string.settings)
.setCheckable(true)
.setIcon(R.drawable.settings)
}
Expand Down Expand Up @@ -580,6 +583,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
}
true
}

else -> super.onOptionsItemSelected(item)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ModelFolderObserver(
Folder.NOTES -> initNotesFolderMenu()
Folder.ARCHIVED -> initArchivedFolderMenu()
Folder.DELETED -> initDeletedFolderMenu()
Folder.HIDDEN -> initHiddenFolderMenu()
}
}

Expand All @@ -64,6 +65,7 @@ class ModelFolderObserver(
baseModel.duplicateSelectedBaseNotes()
}
menu.add(R.string.archive, R.drawable.archive) { moveNotes(Folder.ARCHIVED) }
menu.add(R.string.hidden, R.drawable.hidden) { moveNotes(Folder.HIDDEN) }
menu.addChangeColor()
val pinnedToStatus = menu.addPinnedToStatus()
val share = menu.addShare()
Expand Down Expand Up @@ -100,6 +102,23 @@ class ModelFolderObserver(
model.actionMode.count.observeCount(activity, share)
}

private fun initHiddenFolderMenu() {
menu.add(
R.string.unhidden,
R.drawable.unhidden,
MenuItem.SHOW_AS_ACTION_ALWAYS,
) {
moveNotes(Folder.NOTES)
}
menu.addDelete(MenuItem.SHOW_AS_ACTION_ALWAYS)
menu.addLabels(MenuItem.SHOW_AS_ACTION_ALWAYS)
menu.addExportMenu()
menu.addChangeColor()
val share = menu.add(R.string.share, R.drawable.share) { share() }
model.actionMode.count.observeCount(activity, share)

}

private fun Menu.addPinned(showAsAction: Int = MenuItem.SHOW_AS_ACTION_IF_ROOM): MenuItem {
return add(R.string.pin, R.drawable.pin, showAsAction) {}
}
Expand Down Expand Up @@ -238,10 +257,10 @@ class ModelFolderObserver(
val ids =
baseModel.moveBaseNotes(folderTo) { baseModel.actionMode.loading.postValue(false) }
Snackbar.make(
activity.findViewById(R.id.DrawerLayout),
activity.getQuantityString(folderTo.movedToResId(), ids.size),
Snackbar.LENGTH_SHORT,
)
activity.findViewById(R.id.DrawerLayout),
activity.getQuantityString(folderTo.movedToResId(), ids.size),
Snackbar.LENGTH_SHORT,
)
.apply { setAction(R.string.undo) { baseModel.moveBaseNotes(ids, folderFrom) } }
.show()
} catch (_: Exception) {
Expand All @@ -262,13 +281,13 @@ class ModelFolderObserver(
activity.lifecycleScope.launch {
val deletedNotes = baseModel.deleteSelectedBaseNotes()
Snackbar.make(
activity.findViewById(R.id.DrawerLayout),
activity.getQuantityString(
R.plurals.deleted_selected_notes,
removedNotes.size,
),
Snackbar.LENGTH_SHORT,
)
activity.findViewById(R.id.DrawerLayout),
activity.getQuantityString(
R.plurals.deleted_selected_notes,
removedNotes.size,
),
Snackbar.LENGTH_SHORT,
)
.apply {
setAction(R.string.undo) { baseModel.saveNotes(removedNotes) }
addCallback(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.philkes.notallyx.presentation.activity.main.fragment

import android.os.Bundle
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.WindowManager
import androidx.lifecycle.LiveData
import androidx.navigation.fragment.findNavController
import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.Item
import com.philkes.notallyx.utils.security.showBiometricOrPinPromptHidden

open class HiddenFragment : NotallyFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.folder.value = Folder.HIDDEN
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}

override fun onDestroyView() {
super.onDestroyView()
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}

override fun getBackground() = R.drawable.label_off

override fun getObservable(): LiveData<List<Item>> {
return model.hiddenNotes!!
Comment thread
danmasber marked this conversation as resolved.
}

override fun onStart() {
super.onStart()
hide()
showBiometricOrPinPromptHidden(
Comment on lines +33 to +36
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hide the view earlier to avoid pre-auth flash of hidden content.

hide() runs in onStart (Line 28), but observers/adapters are initialized in onViewCreated of NotallyFragment, so hidden notes can briefly render before auth. Hide the root at the beginning of onViewCreated (or via initial layout state) and reveal only after successful auth.

Suggested patch
 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-    super.onViewCreated(view, savedInstanceState)
+    view.visibility = INVISIBLE
     model.folder.value = Folder.HIDDEN
+    super.onViewCreated(view, savedInstanceState)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/HiddenFragment.kt`
around lines 26 - 29, The root view is being hidden too late (hide() is called
in onStart), causing a brief pre-auth flash because adapters/observers in
NotallyFragment initialize in onViewCreated; move the initial hide to the very
start of onViewCreated (or set the root's initial visibility in its layout) so
the UI is hidden before adapters bind, then keep the existing logic in
showBiometricOrPinPromptHidden to reveal the view after successful auth; update
references around onViewCreated in HiddenFragment (and/or the NotallyFragment
layout) to ensure the root is initially GONE/INVISIBLE and only toggled by
hide() and the post-auth reveal path.

this,
R.string.hidden_lock_title,
onSuccess = { show() },
onFailure = { findNavController().popBackStack() },
)
Comment on lines +36 to +41
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify bypass-prone paths in LockUtils helper used by HiddenFragment.
# Expected: Identify branches where onSuccess.invoke() executes without successful authentication callback.

fd -i "LockUtils.kt" --exec nl -ba {} | sed -n '60,150p'
echo "----"
rg -n --type=kt -C3 'showBiometricOrPinPromptHidden|canAuthenticate\(|promptPinAuthentication\(|onSuccess\.invoke\('

Repository: Crustack/NotallyX

Length of output: 143


🏁 Script executed:

#!/bin/bash
# Locate the key files
echo "=== Finding LockUtils.kt ==="
fd "LockUtils.kt" -t f

echo "=== Finding HiddenFragment.kt ==="
fd "HiddenFragment.kt" -t f

echo "=== Finding NotallyFragment.kt ==="
fd "NotallyFragment.kt" -t f

Repository: Crustack/NotallyX

Length of output: 412


🏁 Script executed:

#!/bin/bash
# Once we locate the files, examine the LockUtils.kt showBiometricOrPinPromptHidden function
# Search for the function and surrounding code
echo "=== Searching for showBiometricOrPinPromptHidden function ==="
rg -n "showBiometricOrPinPromptHidden" --type-add 'kotlin:*.kt' -t kotlin -A 80

Repository: Crustack/NotallyX

Length of output: 13097


🏁 Script executed:

#!/bin/bash
# Search for canAuthenticate and promptPinAuthentication patterns
echo "=== Searching for authentication helper invocations ==="
rg -n "canAuthenticate|promptPinAuthentication|onSuccess\.invoke" --type-add 'kotlin:*.kt' -t kotlin -B 2 -A 2

Repository: Crustack/NotallyX

Length of output: 10380


Hidden-note unlock bypassed on multiple authentication paths.

In showBiometricOrPinPromptHidden (LockUtils.kt:60–138), onSuccess is invoked without confirmed authentication in three scenarios:

  • Line 76: Android M+ with no available authenticator (canAuthenticate fails)
  • Line 130: Pre-M with no biometric capability
  • Line 135: Pre-M with available PIN/password prompt—onSuccess is called immediately after promptPinAuthentication() launches, not after the user authenticates

Line 135 is the most critical: the PIN/password prompt displays but onSuccess executes unconditionally before the user completes authentication, allowing hidden notes to be exposed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/HiddenFragment.kt`
around lines 29 - 34, The showBiometricOrPinPromptHidden flow incorrectly
invokes onSuccess before confirming authentication in multiple branches; update
LockUtils.showBiometricOrPinPromptHidden so that (1) the canAuthenticate failure
path (currently calling onSuccess when Android M+ has no authenticator) instead
triggers onFailure or starts a verified fallback, (2) the pre-M
biometric-unavailable branch does not call onSuccess but routes to the
PIN/password flow or onFailure, and (3) the promptPinAuthentication path waits
for the actual PIN/password verification result instead of calling onSuccess
immediately — refactor promptPinAuthentication to accept a completion callback
(success/failure) and only call onSuccess when that callback reports success,
using the same verified-callback pattern for biometric and PIN paths.

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

protected fun show() {
binding?.root?.visibility = VISIBLE
}

protected fun hide() {
binding?.root?.visibility = INVISIBLE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ class SearchFragment : NotallyFragment() {

val initialLabel = arguments?.getString(EXTRA_INITIAL_LABEL)
model.currentLabel = initialLabel
if (initialLabel?.isEmpty() == true) {
if (initialLabel?.isEmpty() == true && model.folder.value != Folder.HIDDEN) {
val checked =
when (initialFolder ?: model.folder.value) {
Folder.NOTES -> R.id.Notes
Folder.DELETED -> R.id.Deleted
Folder.ARCHIVED -> R.id.Archived
Folder.HIDDEN -> R.id.Hidden
Comment thread
danmasber marked this conversation as resolved.
}

binding?.ChipGroup?.apply {
Expand All @@ -40,6 +41,7 @@ class SearchFragment : NotallyFragment() {
R.id.Notes -> model.folder.value = Folder.NOTES
R.id.Deleted -> model.folder.value = Folder.DELETED
R.id.Archived -> model.folder.value = Folder.ARCHIVED
R.id.Hidden -> model.folder.value = Folder.HIDDEN
}
}
check(checked)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.philkes.notallyx.presentation.activity.note

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
Expand All @@ -12,13 +13,15 @@ import android.os.Looper
import android.text.Editable
import android.text.Spanned
import android.text.style.URLSpan
import android.util.AttributeSet
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.VISIBLE
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.activity.viewModels
import androidx.annotation.ColorInt
Expand All @@ -34,6 +37,7 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.NoteViewMode
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.data.model.generateBaseNote
Expand Down Expand Up @@ -124,6 +128,27 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
}
}

override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
if (notallyModel.folder == Folder.HIDDEN) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
return super.onCreateView(name, context, attrs)
}

override fun onStop() {
super.onStop()
if (notallyModel.folder == Folder.HIDDEN) {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}

override fun onPause() {
if (notallyModel.folder == Folder.HIDDEN) {
finish()
}
super.onPause()
}

override fun finish() {
lifecycleScope.launch(Dispatchers.Main) {
checkSave()
Expand Down Expand Up @@ -183,6 +208,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
Intent.ACTION_SEND,
Intent.ACTION_SEND_MULTIPLE,
Intent.ACTION_VIEW -> handleSharedNote()

else ->
intent.getStringExtra(EXTRA_DISPLAYED_LABEL)?.let {
notallyModel.setLabels(listOf(it))
Expand Down Expand Up @@ -478,6 +504,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
Type.NOTE ->
(manualSize ?: binding.EnterBody.lineCount) >
(if (isInLandscapeMode) 30 else 75)

Type.LIST ->
(manualSize ?: notallyModel.items.size) >
(if (isInLandscapeMode) 15 else 25)
Expand Down Expand Up @@ -647,6 +674,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
binding.EnterTitle.initHistory(changeHistory) { text ->
notallyModel.title = text.trim().toString()
}

val textMaxLengthFilter = application.textMaxLengthFilter()
binding.EnterTitle.filters = textMaxLengthFilter
binding.EnterBody.filters = textMaxLengthFilter
Expand Down Expand Up @@ -688,6 +716,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
NotesSortBy.CREATION_DATE -> Pair(notallyModel.timestamp, R.string.creation_date)
NotesSortBy.MODIFIED_DATE ->
Pair(notallyModel.modifiedTimestamp, R.string.modified_date)

else -> Pair(null, null)
}
binding.Date.apply {
Expand Down Expand Up @@ -943,6 +972,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
binding.MainListView.visibility = GONE
binding.CheckedListView.visibility = GONE
}

Type.LIST -> {
binding.EnterBody.visibility = GONE
binding.CheckedListView.visibility =
Expand Down
Loading