feat: Chinese localization, MIUI fixes, boot stability & performance optimization#22
feat: Chinese localization, MIUI fixes, boot stability & performance optimization#22xunnv wants to merge 7 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds documentation and CI templates, updates build tooling and signing, introduces a scheduling subsystem (data, manager, UI, import/export), adds a hide-recents preference with MainActivity API, hardens BootReceiver and VolumeService startup and polling, and updates adapters/fragments and localized strings (including Simplified Chinese). ChangesRepository docs and templates
CI / Issue / Funding
Build tooling and signing
Manifest, navigation, and resources
Recents preference and Settings
MainActivity behavior
Scheduling: model, manager, UI
Boot and service startup, polling
Adapters, fragments, temporary-unlock
Tests and fastlane metadata
Estimated code review effort: Possibly related PRs:
Suggested reviewers:
Poem:
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/main/res/values-zh/strings.xml`:
- Line 1: The locale folder `values-zh` is too generic for Simplified Chinese;
rename or move this `strings.xml` into a region/script-specific qualifier such
as `values-zh-rCN` or `values-b+zh+Hans` so the translations are scoped to
Simplified Chinese (Hans) only; update any references or merge conflicts
accordingly and keep the same <resources> contents (no content changes needed
inside the `strings.xml`) so Traditional Chinese locales are not served the
Simplified strings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9593d1d6-7e95-4fee-84a1-bbb58dce7af5
📒 Files selected for processing (1)
app/src/main/res/values-zh/strings.xml
| @@ -0,0 +1,25 @@ | |||
| <resources> | |||
There was a problem hiding this comment.
Scope this locale to Simplified Chinese explicitly (zh-CN/Hans).
The translations are Simplified Chinese, but values-zh is a generic Chinese bucket. This can surface Simplified text to users expecting Traditional Chinese. Please move this to a region/script-specific qualifier (for example values-zh-rCN or values-b+zh+Hans).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/res/values-zh/strings.xml` at line 1, The locale folder
`values-zh` is too generic for Simplified Chinese; rename or move this
`strings.xml` into a region/script-specific qualifier such as `values-zh-rCN` or
`values-b+zh+Hans` so the translations are scoped to Simplified Chinese (Hans)
only; update any references or merge conflicts accordingly and keep the same
<resources> contents (no content changes needed inside the `strings.xml`) so
Traditional Chinese locales are not served the Simplified strings.
…mization - i18n(zh): add complete Simplified Chinese translation - fix(boot): resolve ForegroundServiceStartNotAllowedException death loop - Replace stopSelf() with mForegroundFailed flag + START_NOT_STICKY - Add 60s boot delay to avoid audio HAL initialization contention - fix(miui): use AppTask.setExcludeFromRecents for reliable recents hiding - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS is overridden by MIUI - fix(bg): restore VolumeService.start() to keep service alive after unbind - perf(service): replace 25ms Timer with ScheduledExecutorService - 500ms interval, 80% CPU reduction vs original polling - Remove ContentObserver->checkVolumes() chain (12 premature boot calls) - perf(ui): notifyItemChanged instead of notifyDataSetChanged - perf(adapter): cache isPasswordProtected, O(1) lock lookup via HashMap - perf(gson): reuse Gson singleton instance (avoid reflection cache rebuild) - security: remove hardcoded keystore credentials from build.gradle - Read from external release/keystore.properties (git-ignored) - docs: bilingual README, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md - docs: GitHub issue templates (bug report, feature request) and PR template - chore: clean up redundant APKs and unused assets - chore: remove deprecated buildToolsVersion and unused ext.kotlin_version
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (3)
app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt (3)
77-80: ⚡ Quick winTarget the current task instead of assuming
appTasks[0].
appTasksordering is not a stable contract. Match bytaskIdbefore callingsetExcludeFromRecents.Proposed fix
- val tasks = am.appTasks - if (tasks.isNotEmpty()) { - tasks[0].setExcludeFromRecents(exclude) - } + am.appTasks + .firstOrNull { it.taskInfo?.taskId == taskId } + ?.setExcludeFromRecents(exclude)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt` around lines 77 - 80, The current code assumes am.appTasks[0] is the current task; instead iterate am.appTasks to find the TaskInfo whose taskId matches the current activity's taskId (use this.taskId or Activity.getTaskId()) and call setExcludeFromRecents(exclude) only on that matched task; update the block in MainActivity where variables tasks, am and exclude are used to perform a safe lookup by taskId rather than using tasks[0].
97-98: ⚡ Quick winAlign
@RequiresApicontract with actual implementation.
setupWindowInsets()currently uses compat APIs but is marked as Q+ only and called unguarded. Either remove the annotation or gate the call at Q+.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt` around lines 97 - 98, The `@RequiresApi`(Build.VERSION_CODES.Q) annotation on setupWindowInsets() is incorrect because the method uses compat APIs yet is invoked without a runtime guard; either remove the `@RequiresApi` annotation from setupWindowInsets() so it can be called on all API levels, or keep the annotation and add a runtime API-level check (e.g., Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) at each call site that invokes setupWindowInsets(); update the symbol setupWindowInsets and any call sites accordingly so the annotation and runtime behavior are consistent.
26-27: ⚡ Quick winClear delayed callbacks on destroy to avoid stale execution.
postDelayedcan execute after the activity is torn down. Clear pending callbacks inonDestroy().Proposed fix
class MainActivity : AppCompatActivity() { @@ private val handler = Handler(Looper.getMainLooper()) @@ + override fun onDestroy() { + handler.removeCallbacksAndMessages(null) + super.onDestroy() + } + private fun applyRecentsHiding() {Also applies to: 64-67
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt` around lines 26 - 27, The Activity posts delayed work with the Handler declared as "handler" (see uses of handler.postDelayed around lines 64-67); this can fire after the Activity is destroyed—add cleanup in MainActivity.onDestroy() to remove pending callbacks/messages. In onDestroy(), call handler.removeCallbacksAndMessages(null) (or removeCallbacks for specific Runnables referenced where handler.postDelayed is used) to cancel any pending delayed tasks before calling super.onDestroy().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/pull_request_template.md:
- Around line 1-68: The PR template is written as a GitHub issue-form YAML (keys
like name:, about:, body:, type:, id:) which GitHub does not render for pull
requests; replace the YAML with a plain Markdown PR template: add a short
title/description section, headings for "Type of change", "Description",
"Testing", "Checklist" and "Related issue", and convert the YAML
checkboxes/options into a Markdown task list (e.g., - [ ] I have read
CONTRIBUTING.md) and simple placeholders for type and testing details so
contributors see a normal editable PR body instead of raw YAML.
In `@app/build.gradle`:
- Around line 45-55: The keystore loading in app/build.gradle uses
keystoreProperties.load(new FileInputStream(...)) without closing the stream,
doesn't validate required keys, and silently skips signing if the keystore file
is missing; fix by opening the properties file with a try-with-resources style
(use FileInputStream in a .withCloseable or try/finally to close), validate that
keystoreProperties['storeFile'], ['storePassword'], ['keyAlias'], and
['keyPassword'] are non-null/non-blank before applying them to
signingConfigs.release, and if properties are present but the resolved
keystoreFile does not exist fail fast (throw GradleException or logger.error +
throw) instead of silently skipping so build configuration errors are visible;
update the block that references keystorePropertiesFile, keystoreProperties,
signingConfigs.release, and buildTypes.release accordingly.
In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt`:
- Around line 263-266: The comment above invokeVolumeListenerCallback()
incorrectly states a "100ms" response while the actual polling interval is
500ms; update the comment to reflect the real interval (500ms) or change the
polling interval constant to 100ms if you intended faster response. Locate the
polling loop that calls invokeVolumeListenerCallback() and either (A) edit the
comment text to say "500ms" (or reference the polling constant name used, e.g.,
pollingIntervalMs / VOLUME_POLL_INTERVAL_MS) or (B) change the poll interval
constant to 100ms so the comment matches; ensure the comment and the value
referenced by the polling loop remain consistent.
- Around line 140-143: The comment describing the boot delay is inconsistent
with the constant value: it says "延迟 15s 启动" but BOOT_DELAY_MS is 60000L (60s).
Update either the comment or the constant so they match; e.g., if intended delay
is 15s, change BOOT_DELAY_MS to 15000L, or if 60s is correct, change the comment
text to "延迟 60s 启动". Make the edit near the BOOT_DELAY_MS declaration and the
usage in initialDelay/startLocking so both the constant and the inline comment
(lines around BOOT_DELAY_MS, initialDelay, fromBoot, startLocking) reflect the
same delay value.
- Around line 153-171: The KDoc for startLocking incorrectly states a 100ms
polling interval while the code uses POLL_INTERVAL_MS (500ms); update the
documentation to match the actual interval (use POLL_INTERVAL_MS value or
"500ms") or change POLL_INTERVAL_MS if 100ms is intended. Locate the
startLocking function and its KDoc, adjust the text "100ms 间隔轮询" to accurately
reflect POLL_INTERVAL_MS (e.g., "500ms 间隔轮询" or reference POLL_INTERVAL_MS) so
the comment and implementation are consistent.
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 97-100: In onBindViewHolder (VolumeAdapter.kt) the code only sets
UI for locked rows, causing recycled holders to keep prior checked/disabled
states; add an explicit else branch when checking
mService?.getLocks()?.containsKey(volume.stream) to reset the UI for unlocked
rows by setting holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (reference the existing
holder.binding.switchButton and holder.binding.slider to locate where to add the
else).
- Around line 33-44: In update(volumes: List<Volume>) capture the old size
(mVolumeList.size) before assigning mVolumeList = volumes, then handle size
changes first: if volumes.size > oldSize call notifyItemRangeInserted(oldSize,
volumes.size - oldSize) (or notifyItemInserted per index) for new items; if
volumes.size < oldSize call notifyItemRangeRemoved(volumes.size, oldSize -
volumes.size) (or notifyItemRemoved per index) for removed items; after handling
inserts/removes compute changed positions only over the overlapping range (0
until min(oldSize, volumes.size)) comparing volumes[i].value/locked with
mVolumeList[i] and call notifyItemChanged(i) for those indices; finally assign
mVolumeList = volumes. This ensures insertions/removals are announced before
per-item change notifications.
In `@CODE_OF_CONDUCT.md`:
- Around line 57-60: The enforcement contact is still a placeholder in the
sentence starting "Instances of abusive, harassing, or otherwise unacceptable
behavior may be reported to the community leaders responsible for enforcement at
the repository owner's contact information."; replace that placeholder with a
concrete contact method (for example a maintainer/security email like
security@your-org.com or a link to the repo's issues/discussions route or a
pointer to SECURITY.md) so reporters have an actionable channel; update the same
sentence in CODE_OF_CONDUCT.md to include that real email or specific
issue/discussion route and optionally mention expected response/timeframe.
In `@CONTRIBUTING.md`:
- Line 143: Update the malformed BCP 47 example under the "Locale codes" section
by replacing the incorrect tag `ru-rRU` with the correct `ru-RU`; ensure the
example list (`zh`, `fr`, `ru-RU`) follows BCP 47 casing/format rules (or add a
separate note if you intend to document Android resource qualifier format
separately).
- Line 41: Add blank lines before and after the Markdown table that starts with
"| Prefix | Purpose |" so it doesn't trigger MD058, and annotate any fenced code
blocks mentioned at lines 73, 98, 109, and 149 with an explicit language (e.g.,
use ```text or ```bash as appropriate) to resolve MD040; update the
CONTRIBUTING.md by locating the table header string "| Prefix | Purpose |" and
the fenced code blocks at the referenced locations and insert a blank line above
and below the table and add the language specifier to each fence.
---
Nitpick comments:
In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt`:
- Around line 77-80: The current code assumes am.appTasks[0] is the current
task; instead iterate am.appTasks to find the TaskInfo whose taskId matches the
current activity's taskId (use this.taskId or Activity.getTaskId()) and call
setExcludeFromRecents(exclude) only on that matched task; update the block in
MainActivity where variables tasks, am and exclude are used to perform a safe
lookup by taskId rather than using tasks[0].
- Around line 97-98: The `@RequiresApi`(Build.VERSION_CODES.Q) annotation on
setupWindowInsets() is incorrect because the method uses compat APIs yet is
invoked without a runtime guard; either remove the `@RequiresApi` annotation from
setupWindowInsets() so it can be called on all API levels, or keep the
annotation and add a runtime API-level check (e.g., Build.VERSION.SDK_INT >=
Build.VERSION_CODES.Q) at each call site that invokes setupWindowInsets();
update the symbol setupWindowInsets and any call sites accordingly so the
annotation and runtime behavior are consistent.
- Around line 26-27: The Activity posts delayed work with the Handler declared
as "handler" (see uses of handler.postDelayed around lines 64-67); this can fire
after the Activity is destroyed—add cleanup in MainActivity.onDestroy() to
remove pending callbacks/messages. In onDestroy(), call
handler.removeCallbacksAndMessages(null) (or removeCallbacks for specific
Runnables referenced where handler.postDelayed is used) to cancel any pending
delayed tasks before calling super.onDestroy().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 908b14c0-b330-4576-be27-654219a00453
⛔ Files ignored due to path filters (2)
app/src/main/ic_volumelockr-playstore.pngis excluded by!**/*.pngapp/src/main/screenshot.pngis excluded by!**/*.png
📒 Files selected for processing (20)
.github/ISSUE_TEMPLATE/bug_report.yml.github/ISSUE_TEMPLATE/feature_request.yml.github/pull_request_template.md.gitignoreCHANGELOG.mdCODE_OF_CONDUCT.mdCONTRIBUTING.mdREADME.mdapp/build.gradleapp/src/main/AndroidManifest.xmlapp/src/main/java/com/klee/volumelockr/BootReceiver.ktapp/src/main/java/com/klee/volumelockr/service/VolumeService.ktapp/src/main/java/com/klee/volumelockr/ui/MainActivity.ktapp/src/main/java/com/klee/volumelockr/ui/SettingsFragment.ktapp/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.ktapp/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.ktapp/src/main/res/values-zh/strings.xmlapp/src/main/res/values/strings.xmlapp/src/main/res/xml/root_preferences.xmlbuild.gradle
💤 Files with no reviewable changes (1)
- build.gradle
✅ Files skipped from review due to trivial changes (6)
- app/src/main/res/xml/root_preferences.xml
- .github/ISSUE_TEMPLATE/bug_report.yml
- .github/ISSUE_TEMPLATE/feature_request.yml
- CHANGELOG.md
- README.md
- app/src/main/res/values-zh/strings.xml
| if (keystorePropertiesFile.exists()) { | ||
| def keystoreProperties = new Properties() | ||
| keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) | ||
| def keystoreFile = rootProject.file(keystoreProperties['storeFile']) | ||
| if (keystoreFile.exists()) { | ||
| signingConfigs.release.storeFile = keystoreFile | ||
| signingConfigs.release.storePassword = keystoreProperties['storePassword'] | ||
| signingConfigs.release.keyAlias = keystoreProperties['keyAlias'] | ||
| signingConfigs.release.keyPassword = keystoreProperties['keyPassword'] | ||
| buildTypes.release.signingConfig = signingConfigs.release | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
sed -n '44,56p' app/build.gradle
printf '\n-- direct property lookups --\n'
rg -n "keystoreProperties\\['(storeFile|storePassword|keyAlias|keyPassword)'\\]" app/build.gradle
printf '\n-- existing validation / scoped-close checks --\n'
rg -n "withInputStream|withCloseable|GradleException|missingKeys|containsKey" app/build.gradleRepository: jonathanklee/VolumeLockr
Length of output: 1253
🏁 Script executed:
#!/bin/bash
rg -n "FileInputStream|withInputStream|withCloseable|close\\(" app/build.gradleRepository: jonathanklee/VolumeLockr
Length of output: 148
Fail fast & properly scope keystore signing property loading (app/build.gradle)
app/build.gradleloadskeystorePropertieswithkeystoreProperties.load(new FileInputStream(...))without scoping/closing the stream.- Required keys (
storeFile,storePassword,keyAlias,keyPassword) are accessed directly; missing/blank values can fail during Gradle configuration with opaque errors. - If the referenced keystore file doesn’t exist, signing is silently skipped, making release signing issues easy to miss.
Proposed fix
if (keystorePropertiesFile.exists()) {
def keystoreProperties = new Properties()
- keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
- def keystoreFile = rootProject.file(keystoreProperties['storeFile'])
- if (keystoreFile.exists()) {
- signingConfigs.release.storeFile = keystoreFile
- signingConfigs.release.storePassword = keystoreProperties['storePassword']
- signingConfigs.release.keyAlias = keystoreProperties['keyAlias']
- signingConfigs.release.keyPassword = keystoreProperties['keyPassword']
- buildTypes.release.signingConfig = signingConfigs.release
- }
+ keystorePropertiesFile.withInputStream { stream ->
+ keystoreProperties.load(stream)
+ }
+
+ def requiredKeys = ['storeFile', 'storePassword', 'keyAlias', 'keyPassword']
+ def missingKeys = requiredKeys.findAll { !keystoreProperties[it] }
+ if (!missingKeys.isEmpty()) {
+ throw new GradleException(
+ "Invalid release/keystore.properties. Missing: ${missingKeys.join(', ')}"
+ )
+ }
+
+ def keystoreFile = rootProject.file(keystoreProperties['storeFile'])
+ if (!keystoreFile.exists()) {
+ throw new GradleException("Keystore file not found: ${keystoreFile}")
+ }
+
+ signingConfigs.release.storeFile = keystoreFile
+ signingConfigs.release.storePassword = keystoreProperties['storePassword']
+ signingConfigs.release.keyAlias = keystoreProperties['keyAlias']
+ signingConfigs.release.keyPassword = keystoreProperties['keyPassword']
+ buildTypes.release.signingConfig = signingConfigs.release
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/build.gradle` around lines 45 - 55, The keystore loading in
app/build.gradle uses keystoreProperties.load(new FileInputStream(...)) without
closing the stream, doesn't validate required keys, and silently skips signing
if the keystore file is missing; fix by opening the properties file with a
try-with-resources style (use FileInputStream in a .withCloseable or try/finally
to close), validate that keystoreProperties['storeFile'], ['storePassword'],
['keyAlias'], and ['keyPassword'] are non-null/non-blank before applying them to
signingConfigs.release, and if properties are present but the resolved
keystoreFile does not exist fail fast (throw GradleException or logger.error +
throw) instead of silently skipping so build configuration errors are visible;
update the block that references keystorePropertiesFile, keystoreProperties,
signingConfigs.release, and buildTypes.release accordingly.
| /** | ||
| * 启动音量锁定:开启 100ms 间隔轮询。 | ||
| * @param initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景) | ||
| */ | ||
| @Synchronized | ||
| fun startLocking() { | ||
| if (mTimer != null) { | ||
| fun startLocking(initialDelayMs: Long = 0) { | ||
| if (mPollingFuture != null) { | ||
| Log.d(TAG, "startLocking: 已在轮询中,跳过 (callDelay=${initialDelayMs}ms)") | ||
| return | ||
| } | ||
|
|
||
| mTimer = Timer() | ||
| mTimer?.scheduleAtFixedRate( | ||
| object : TimerTask() { | ||
| override fun run() { | ||
| checkVolumes() | ||
| } | ||
| }, | ||
| 0, | ||
| PERIOD_IN_MS | ||
| mCheckCount = 0 | ||
| Log.i(TAG, "startLocking: 启动轮询, initialDelay=${initialDelayMs}ms, interval=${POLL_INTERVAL_MS}ms, locks=${mVolumeLock.size}") | ||
| mPollingFuture = mCheckExecutor.scheduleWithFixedDelay( | ||
| { checkVolumes() }, | ||
| initialDelayMs, | ||
| POLL_INTERVAL_MS, | ||
| TimeUnit.MILLISECONDS | ||
| ) | ||
| } |
There was a problem hiding this comment.
Javadoc inconsistency: says 100ms but interval is 500ms.
The Javadoc on line 154 states "100ms 间隔轮询" (100ms interval polling), but POLL_INTERVAL_MS is 500L (500ms).
Proposed fix
/**
- * 启动音量锁定:开启 100ms 间隔轮询。
+ * 启动音量锁定:开启 500ms 间隔轮询。
* `@param` initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景)
*/📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * 启动音量锁定:开启 100ms 间隔轮询。 | |
| * @param initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景) | |
| */ | |
| @Synchronized | |
| fun startLocking() { | |
| if (mTimer != null) { | |
| fun startLocking(initialDelayMs: Long = 0) { | |
| if (mPollingFuture != null) { | |
| Log.d(TAG, "startLocking: 已在轮询中,跳过 (callDelay=${initialDelayMs}ms)") | |
| return | |
| } | |
| mTimer = Timer() | |
| mTimer?.scheduleAtFixedRate( | |
| object : TimerTask() { | |
| override fun run() { | |
| checkVolumes() | |
| } | |
| }, | |
| 0, | |
| PERIOD_IN_MS | |
| mCheckCount = 0 | |
| Log.i(TAG, "startLocking: 启动轮询, initialDelay=${initialDelayMs}ms, interval=${POLL_INTERVAL_MS}ms, locks=${mVolumeLock.size}") | |
| mPollingFuture = mCheckExecutor.scheduleWithFixedDelay( | |
| { checkVolumes() }, | |
| initialDelayMs, | |
| POLL_INTERVAL_MS, | |
| TimeUnit.MILLISECONDS | |
| ) | |
| } | |
| /** | |
| * 启动音量锁定:开启 500ms 间隔轮询。 | |
| * `@param` initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景) | |
| */ | |
| `@Synchronized` | |
| fun startLocking(initialDelayMs: Long = 0) { | |
| if (mPollingFuture != null) { | |
| Log.d(TAG, "startLocking: 已在轮询中,跳过 (callDelay=${initialDelayMs}ms)") | |
| return | |
| } | |
| mCheckCount = 0 | |
| Log.i(TAG, "startLocking: 启动轮询, initialDelay=${initialDelayMs}ms, interval=${POLL_INTERVAL_MS}ms, locks=${mVolumeLock.size}") | |
| mPollingFuture = mCheckExecutor.scheduleWithFixedDelay( | |
| { checkVolumes() }, | |
| initialDelayMs, | |
| POLL_INTERVAL_MS, | |
| TimeUnit.MILLISECONDS | |
| ) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt` around lines
153 - 171, The KDoc for startLocking incorrectly states a 100ms polling interval
while the code uses POLL_INTERVAL_MS (500ms); update the documentation to match
the actual interval (use POLL_INTERVAL_MS value or "500ms") or change
POLL_INTERVAL_MS if 100ms is intended. Locate the startLocking function and its
KDoc, adjust the text "100ms 间隔轮询" to accurately reflect POLL_INTERVAL_MS (e.g.,
"500ms 间隔轮询" or reference POLL_INTERVAL_MS) so the comment and implementation
are consistent.
| // 仅通知 UI 更新,不触发 checkVolumes()。 | ||
| // 轮询循环 100ms 已足够快速响应(<0.1s 延迟,人无法感知), | ||
| // ContentObserver 触发检查会在开机初期引发 12 次音频 HAL 争抢导致卡顿。 | ||
| invokeVolumeListenerCallback() |
There was a problem hiding this comment.
Comment inconsistency: says 100ms but interval is 500ms.
The comment references "100ms" response time, but the actual polling interval is 500ms.
Proposed fix
- // 轮询循环 100ms 已足够快速响应(<0.1s 延迟,人无法感知),
+ // 轮询循环 500ms 已足够快速响应(<0.5s 延迟,人无法感知),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt` around lines
263 - 266, The comment above invokeVolumeListenerCallback() incorrectly states a
"100ms" response while the actual polling interval is 500ms; update the comment
to reflect the real interval (500ms) or change the polling interval constant to
100ms if you intended faster response. Locate the polling loop that calls
invokeVolumeListenerCallback() and either (A) edit the comment text to say
"500ms" (or reference the polling constant name used, e.g., pollingIntervalMs /
VOLUME_POLL_INTERVAL_MS) or (B) change the poll interval constant to 100ms so
the comment matches; ensure the comment and the value referenced by the polling
loop remain consistent.
| fun update(volumes: List<Volume>) { | ||
| // 找出实际变化的项,避免整体刷新 | ||
| val changedPositions = mutableListOf<Int>() | ||
| for (i in volumes.indices) { | ||
| if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value | ||
| || volumes[i].locked != mVolumeList[i].locked) { | ||
| changedPositions.add(i) | ||
| } | ||
| } | ||
| mVolumeList = volumes | ||
| changedPositions.forEach { notifyItemChanged(it) } | ||
| } |
There was a problem hiding this comment.
Handle list size changes before partial item notifications.
Line 43 only dispatches notifyItemChanged, so insertions/removals are never announced. If volumes.size changes, RecyclerView can get out of sync with adapter updates.
Suggested fix
`@MainThread`
fun update(volumes: List<Volume>) {
+ if (volumes.size != mVolumeList.size) {
+ mVolumeList = volumes
+ notifyDataSetChanged()
+ return
+ }
+
// 找出实际变化的项,避免整体刷新
val changedPositions = mutableListOf<Int>()
for (i in volumes.indices) {
- if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value
- || volumes[i].locked != mVolumeList[i].locked) {
+ if (volumes[i].value != mVolumeList[i].value
+ || volumes[i].locked != mVolumeList[i].locked) {
changedPositions.add(i)
}
}
mVolumeList = volumes
changedPositions.forEach { notifyItemChanged(it) }
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 33 -
44, In update(volumes: List<Volume>) capture the old size (mVolumeList.size)
before assigning mVolumeList = volumes, then handle size changes first: if
volumes.size > oldSize call notifyItemRangeInserted(oldSize, volumes.size -
oldSize) (or notifyItemInserted per index) for new items; if volumes.size <
oldSize call notifyItemRangeRemoved(volumes.size, oldSize - volumes.size) (or
notifyItemRemoved per index) for removed items; after handling inserts/removes
compute changed positions only over the overlapping range (0 until min(oldSize,
volumes.size)) comparing volumes[i].value/locked with mVolumeList[i] and call
notifyItemChanged(i) for those indices; finally assign mVolumeList = volumes.
This ensures insertions/removals are announced before per-item change
notifications.
| if (mService?.getLocks()?.containsKey(volume.stream) == true) { | ||
| holder.binding.switchButton.isChecked = true | ||
| holder.binding.slider.isEnabled = false | ||
| } |
There was a problem hiding this comment.
Reset unlocked UI state during bind to avoid stale rows.
Line 97 only applies state for locked rows. Unlocked rows are not explicitly reset, so recycled holders can keep old checked/disabled state.
Suggested fix
private fun loadLockFromService(holder: ViewHolder, volume: Volume) {
- if (mService?.getLocks()?.containsKey(volume.stream) == true) {
- holder.binding.switchButton.isChecked = true
- holder.binding.slider.isEnabled = false
- }
+ val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true
+ holder.binding.switchButton.isChecked = isLocked
+ holder.binding.slider.isEnabled = !isLocked
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (mService?.getLocks()?.containsKey(volume.stream) == true) { | |
| holder.binding.switchButton.isChecked = true | |
| holder.binding.slider.isEnabled = false | |
| } | |
| private fun loadLockFromService(holder: ViewHolder, volume: Volume) { | |
| val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true | |
| holder.binding.switchButton.isChecked = isLocked | |
| holder.binding.slider.isEnabled = !isLocked | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 97 -
100, In onBindViewHolder (VolumeAdapter.kt) the code only sets UI for locked
rows, causing recycled holders to keep prior checked/disabled states; add an
explicit else branch when checking
mService?.getLocks()?.containsKey(volume.stream) to reset the UI for unlocked
rows by setting holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (reference the existing
holder.binding.switchButton and holder.binding.slider to locate where to add the
else).
| Instances of abusive, harassing, or otherwise unacceptable behavior may be | ||
| reported to the community leaders responsible for enforcement at the | ||
| repository owner's contact information. All complaints will be reviewed and | ||
| investigated promptly and fairly. |
There was a problem hiding this comment.
Enforcement contact is still a placeholder.
Line 59 should include a real contact method (email or issue/discussion route). The current placeholder leaves reporters without an actionable path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CODE_OF_CONDUCT.md` around lines 57 - 60, The enforcement contact is still a
placeholder in the sentence starting "Instances of abusive, harassing, or
otherwise unacceptable behavior may be reported to the community leaders
responsible for enforcement at the repository owner's contact information.";
replace that placeholder with a concrete contact method (for example a
maintainer/security email like security@your-org.com or a link to the repo's
issues/discussions route or a pointer to SECURITY.md) so reporters have an
actionable channel; update the same sentence in CODE_OF_CONDUCT.md to include
that real email or specific issue/discussion route and optionally mention
expected response/timeframe.
| ``` | ||
|
|
||
| Use one of these branch prefixes: | ||
| | Prefix | Purpose | |
There was a problem hiding this comment.
Resolve markdownlint warnings in this doc block.
Current content triggers MD058 (table spacing) and MD040 (fenced code blocks missing language). Please add blank lines around the table and specify code fence languages (e.g., text where needed) to keep docs lint-clean.
Also applies to: 73-73, 98-98, 109-109, 149-149
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 41-41: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CONTRIBUTING.md` at line 41, Add blank lines before and after the Markdown
table that starts with "| Prefix | Purpose |" so it doesn't trigger MD058, and
annotate any fenced code blocks mentioned at lines 73, 98, 109, and 149 with an
explicit language (e.g., use ```text or ```bash as appropriate) to resolve
MD040; update the CONTRIBUTING.md by locating the table header string "| Prefix
| Purpose |" and the fenced code blocks at the referenced locations and insert a
blank line above and below the table and add the language specifier to each
fence.
| 3. Add your translation credit to the [Translators](README.md#translators) section of README | ||
| 4. Submit as `i18n(<locale>): add/update <language> translations` | ||
|
|
||
| **Locale codes**: Use [BCP 47](https://tools.ietf.org/html/bcp47) format (e.g., `zh`, `fr`, `ru-rRU`). |
There was a problem hiding this comment.
Locale example is malformed.
Line 143 uses ru-rRU, which is not a valid BCP 47 tag format. Use ru-RU (or explicitly document Android resource qualifier format separately).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CONTRIBUTING.md` at line 143, Update the malformed BCP 47 example under the
"Locale codes" section by replacing the incorrect tag `ru-rRU` with the correct
`ru-RU`; ensure the example list (`zh`, `fr`, `ru-RU`) follows BCP 47
casing/format rules (or add a separate note if you intend to document Android
resource qualifier format separately).
新增功能: - 定时音量锁定:按时间段自动锁定音量,多时段、工作日/周末、每音频流独立目标 - 手动临时解锁:定时锁定期间可手动解除 15/30/60/120 分钟 - 调度配置 JSON 导入/导出:备份与恢复定时计划 - ScheduleData/ScheduleManager/ScheduleFragment/ScheduleAdapter 完整调度子系统 修复: - 定时调度完全不生效(VolumeService 仅检查手动锁定项决定是否停止服务) - 移除所有手动锁后调度轮询意外停止(VolumeAdapter.adjustService bug) - 开机后定时调度未自动启动(BootReceiver 未检查调度配置) - JSON 导入的 CUSTOM day type 编辑后类型丢失为 WEEKEND 项目规范化: - security-crypto 1.1.0-alpha06 -> 1.1.0 stable - CI actions v3 -> v4,Detekt toolVersion 同步 1.23.1 -> 1.23.8 - JVM target 1.8 -> 17,移除废弃 buildConfig flag - 新增 .editorconfig、SECURITY.md - 修复 PR 模板格式(YAML issue form -> Markdown 标准模板) - CONTRIBUTING/CHANGELOG 链接更新至 xunnv 仓库 - 清理未使用的 res/menu/options.xml - README 中英文版同步新增调度功能介绍
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
.github/workflows/android.yml (2)
15-21:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPin GitHub Actions to commit SHAs and disable checkout credential persistence
android.ymluses movableactions/checkout@v4andactions/setup-java@v4tags, and the checkout step does not setpersist-credentials: false(no explicit workflowpermissions:block either).Suggested hardening
- - uses: actions/checkout@v4 + - uses: actions/checkout@<pinned_checkout_sha> + with: + persist-credentials: false @@ - - name: set up JDK 17 - uses: actions/setup-java@v4 + - name: set up JDK 17 + uses: actions/setup-java@<pinned_setup_java_sha>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/android.yml around lines 15 - 21, The workflow uses movable tags for actions and leaves checkout credentials persistent; update the actions references (actions/checkout and actions/setup-java) to use their commit SHAs instead of `@v4`, add persist-credentials: false to the actions/checkout step, and add a minimal repository-level permissions block to restrict unnecessary write scopes; locate the checkout step (uses: actions/checkout) and change it to include persist-credentials: false, and locate the setup-java step (uses: actions/setup-java) and pin it to the corresponding commit SHA.
9-13:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDeclare least-privilege workflow permissions explicitly.
.github/workflows/android.ymljobbuildhas nopermissions:block, so it relies on GitHub defaults. Add a least-privilege permissions block (e.g., foractions/checkout):Suggested hardening
jobs: build: + permissions: + contents: read🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/android.yml around lines 9 - 13, Add an explicit least-privilege permissions block to the GitHub Actions workflow for the "build" job: declare the job-level permissions (e.g., set contents: read to allow actions/checkout and any other minimal required scopes) instead of relying on repository defaults; update the "build" job definition in .github/workflows/android.yml to include this permissions block so the workflow runs with only the necessary privileges..github/ISSUE_TEMPLATE/bug_report.yml (1)
80-80:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate logcat filter tag in bug template (Line 80).
The command filters on
VolumeService, but current logging usesVolumeLockr; reporters may miss key boot/schedule logs.Proposed fix
- Run `adb logcat -s VolumeService:V -d > log.txt` and attach the file here. + Run `adb logcat -s VolumeLockr:V -d > log.txt` and attach the file here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/ISSUE_TEMPLATE/bug_report.yml at line 80, Update the logcat command tag from "VolumeService" to "VolumeLockr" in the bug report template so reporters capture current logs; locate the string "adb logcat -s VolumeService:V -d > log.txt" and replace "VolumeService" with "VolumeLockr", and also search the template for any other occurrences of "VolumeService" to update them consistently.
♻️ Duplicate comments (3)
app/src/main/java/com/klee/volumelockr/service/VolumeService.kt (1)
173-174:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate stale 100ms comments to match 500ms polling.
Line 173 and Line 350 still document 100ms while
POLL_INTERVAL_MSis 500ms, which is misleading during maintenance/debugging. This was already reported earlier and is still valid.Also applies to: 350-351
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt` around lines 173 - 174, The javadoc comments in VolumeService that describe the polling interval are stale (they say 100ms) while the actual constant POLL_INTERVAL_MS is 500ms; update the comments near the method that starts polling (the doc for startPolling/initialDelayMs) and the other occurrence around line ~350 to reflect 500ms so documentation matches the POLL_INTERVAL_MS constant and avoids confusion during maintenance.app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt (2)
36-47:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandle insert/remove notifications before partial updates.
Line 40 and Line 46 still only emit
notifyItemChanged, so list size changes can desync adapter state (append/remove paths). This was already reported earlier and is still valid.Suggested fix
`@MainThread` fun update(volumes: List<Volume>) { - // 找出实际变化的项,避免整体刷新 - val changedPositions = mutableListOf<Int>() - for (i in volumes.indices) { - if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value - || volumes[i].locked != mVolumeList[i].locked) { - changedPositions.add(i) - } - } - mVolumeList = volumes - changedPositions.forEach { notifyItemChanged(it) } + val oldList = mVolumeList + val oldSize = oldList.size + val newSize = volumes.size + val overlap = minOf(oldSize, newSize) + + mVolumeList = volumes + + if (newSize > oldSize) { + notifyItemRangeInserted(oldSize, newSize - oldSize) + } else if (newSize < oldSize) { + notifyItemRangeRemoved(newSize, oldSize - newSize) + } + + for (i in 0 until overlap) { + if (volumes[i].value != oldList[i].value || volumes[i].locked != oldList[i].locked) { + notifyItemChanged(i) + } + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 36 - 47, In update(volumes: List<Volume>) the adapter only calls notifyItemChanged for differing indices which breaks when list size changes; compute diffs against the old list (store old = mVolumeList), detect inserts and removes (e.g., if volumes.size > old.size call notifyItemRangeInserted for the new tail or call notifyItemRangeRemoved when smaller, or emit per-index notifyItemInserted/notifyItemRemoved if not only tail changes) and apply those notifications before calling notifyItemChanged for positions in changedPositions, then assign mVolumeList = volumes; reference update, mVolumeList, changedPositions, and notifyItemChanged/notifyItemInserted/notifyItemRemoved/notifyItemRangeInserted/notifyItemRangeRemoved when making the change.
99-104:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReset unlocked row state explicitly during bind.
Line 100-103 only applies locked UI state; unlocked rows are not reset, so recycled holders can show stale checked/disabled controls. This was already reported earlier and is still valid.
Suggested fix
private fun loadLockFromService(holder: ViewHolder, volume: Volume) { - if (mService?.getLocks()?.containsKey(volume.stream) == true) { - holder.binding.switchButton.isChecked = true - holder.binding.slider.isEnabled = false - } + val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true + holder.binding.switchButton.isChecked = isLocked + holder.binding.slider.isEnabled = !isLocked }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 99 - 104, The loadLockFromService method only sets the locked UI state and leaves unlocked rows unchanged, causing recycled ViewHolders to display stale controls; update loadLockFromService to explicitly handle both cases: if mService?.getLocks()?.containsKey(volume.stream) == true then set holder.binding.switchButton.isChecked = true and holder.binding.slider.isEnabled = false, else set holder.binding.switchButton.isChecked = false and holder.binding.slider.isEnabled = true (handle mService null-safely as needed) so every bind resets the row state.
🧹 Nitpick comments (1)
app/src/main/res/layout/item_schedule.xml (1)
58-62: ⚡ Quick winAdd an explicit accessibility label for the switch.
switchSlotEnabledmay be announced without context in TalkBack. Please set a per-itemcontentDescription(e.g., slot name + enabled state) when binding in the adapter so users can identify which schedule they’re toggling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/res/layout/item_schedule.xml` around lines 58 - 62, The switch with id switchSlotEnabled lacks an accessibility label; update the adapter's binding logic (e.g., in the ViewHolder.bind or onBindViewHolder of your ScheduleAdapter) to set a per-item contentDescription that combines the slot name and its enabled state (for example: "<slotName>, enabled" or "<slotName>, disabled") whenever you set the switch's checked value so TalkBack announces meaningful context for each switch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/pull_request_template.md:
- Around line 23-27: The fenced code block in .github/pull_request_template.md
is missing a language and triggers markdownlint MD040; update the
triple-backtick opening fence to include a language (e.g., change ``` to
```text) for the block that contains the three checklist lines so the code fence
becomes a language-specified block and satisfies the linter.
In `@app/src/main/java/com/klee/volumelockr/schedule/ScheduleData.kt`:
- Around line 35-52: The matchesDay + matchesTime split fails for cross-midnight
windows because matchesTime after-midnight should be validated against the
previous-day rules; update logic so that when crossesMidnight is true and
nowMinutes < endMinutes you call matchesDay(previousDay) (compute previousDay =
(dayOfWeek - 1 mod 7) consistent with your day numbering) instead of
matchesDay(currentDay), while keeping existing behavior for nowMinutes >=
startMinutes; modify the caller or add a helper (or a new combined method) that
uses matchesTime(hour, minute) together with dayType/customDays and this
previous-day rule so CUSTOM/WEEKEND/WORKDAY are evaluated correctly for the
after-midnight portion (refer to matchesDay, matchesTime, crossesMidnight,
startMinutes, endMinutes, customDays).
In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt`:
- Around line 55-60: The getDayTypeLabel function currently maps DayType.CUSTOM
to the everyday label; update getDayTypeLabel (in ScheduleAdapter) so
DayType.CUSTOM returns a distinct localized string (e.g.,
context.getString(com.klee.volumelockr.R.string.schedule_custom)) instead of
schedule_everyday, and add the corresponding schedule_custom entry to your
strings.xml resources with the proper translation(s).
- Around line 44-46: The binding sequence is causing recycled Switch listeners
to fire during bind; in onBindViewHolder (where holder.binding.switchSlotEnabled
is used) clear the old listener first by calling
holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null), then set
holder.binding.switchSlotEnabled.isChecked = slot.enabled, and finally reattach
the new listener via setOnCheckedChangeListener { _, checked -> ... } so the
recycled callbacks (e.g., onToggle) are not triggered during view binding.
In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt`:
- Around line 110-111: In ScheduleFragment update the calculation of remaining
minutes so it can never be negative: compute the delta as
(config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000 and clamp it
to zero (e.g., using Math.max or Kotlin's coerceAtLeast) before passing it to
getString; update the reference where binding.tvTempUnlockText.text is set to
use the non-negative remaining value.
- Line 401: The import path in ScheduleFragment's code uses
requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
without closing the stream; update the import logic to scope-close the
InputStream and the BufferedReader by using Kotlin's use blocks (e.g.,
openInputStream(uri)?.use { input -> input.bufferedReader().use { reader ->
reader.readText() } }) so the file descriptor is always released, and assign
that result to the existing json variable.
In `@app/src/main/res/values/strings.xml`:
- Line 66: Update the string resource named schedule_lock_active_msg to remove
the extra spaces around the %s placeholder (and ensure any surrounding quotation
marks are adjacent to %s rather than separated by spaces) so the formatted app
name renders without awkward gaps; locate schedule_lock_active_msg in the
strings.xml and adjust the text accordingly.
In `@CHANGELOG.md`:
- Around line 104-105: Remove or repurpose the unused markdown link reference
"[1.7.1-zh]" in the CHANGELOG: either delete the `[1.7.1-zh]:
https://github.com/xunnv/VolumeLockr/compare/v1.7.1...v1.7.1-zh` reference or
add a corresponding changelog section that references `[1.7.1-zh]` so the label
is actually used and markdownlint will no longer flag it.
---
Outside diff comments:
In @.github/ISSUE_TEMPLATE/bug_report.yml:
- Line 80: Update the logcat command tag from "VolumeService" to "VolumeLockr"
in the bug report template so reporters capture current logs; locate the string
"adb logcat -s VolumeService:V -d > log.txt" and replace "VolumeService" with
"VolumeLockr", and also search the template for any other occurrences of
"VolumeService" to update them consistently.
In @.github/workflows/android.yml:
- Around line 15-21: The workflow uses movable tags for actions and leaves
checkout credentials persistent; update the actions references (actions/checkout
and actions/setup-java) to use their commit SHAs instead of `@v4`, add
persist-credentials: false to the actions/checkout step, and add a minimal
repository-level permissions block to restrict unnecessary write scopes; locate
the checkout step (uses: actions/checkout) and change it to include
persist-credentials: false, and locate the setup-java step (uses:
actions/setup-java) and pin it to the corresponding commit SHA.
- Around line 9-13: Add an explicit least-privilege permissions block to the
GitHub Actions workflow for the "build" job: declare the job-level permissions
(e.g., set contents: read to allow actions/checkout and any other minimal
required scopes) instead of relying on repository defaults; update the "build"
job definition in .github/workflows/android.yml to include this permissions
block so the workflow runs with only the necessary privileges.
---
Duplicate comments:
In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt`:
- Around line 173-174: The javadoc comments in VolumeService that describe the
polling interval are stale (they say 100ms) while the actual constant
POLL_INTERVAL_MS is 500ms; update the comments near the method that starts
polling (the doc for startPolling/initialDelayMs) and the other occurrence
around line ~350 to reflect 500ms so documentation matches the POLL_INTERVAL_MS
constant and avoids confusion during maintenance.
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 36-47: In update(volumes: List<Volume>) the adapter only calls
notifyItemChanged for differing indices which breaks when list size changes;
compute diffs against the old list (store old = mVolumeList), detect inserts and
removes (e.g., if volumes.size > old.size call notifyItemRangeInserted for the
new tail or call notifyItemRangeRemoved when smaller, or emit per-index
notifyItemInserted/notifyItemRemoved if not only tail changes) and apply those
notifications before calling notifyItemChanged for positions in
changedPositions, then assign mVolumeList = volumes; reference update,
mVolumeList, changedPositions, and
notifyItemChanged/notifyItemInserted/notifyItemRemoved/notifyItemRangeInserted/notifyItemRangeRemoved
when making the change.
- Around line 99-104: The loadLockFromService method only sets the locked UI
state and leaves unlocked rows unchanged, causing recycled ViewHolders to
display stale controls; update loadLockFromService to explicitly handle both
cases: if mService?.getLocks()?.containsKey(volume.stream) == true then set
holder.binding.switchButton.isChecked = true and holder.binding.slider.isEnabled
= false, else set holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (handle mService null-safely as needed)
so every bind resets the row state.
---
Nitpick comments:
In `@app/src/main/res/layout/item_schedule.xml`:
- Around line 58-62: The switch with id switchSlotEnabled lacks an accessibility
label; update the adapter's binding logic (e.g., in the ViewHolder.bind or
onBindViewHolder of your ScheduleAdapter) to set a per-item contentDescription
that combines the slot name and its enabled state (for example: "<slotName>,
enabled" or "<slotName>, disabled") whenever you set the switch's checked value
so TalkBack announces meaningful context for each switch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 75d3ff6a-74ed-421e-b0df-9bf0e02f6311
📒 Files selected for processing (30)
.editorconfig.github/FUNDING.yml.github/ISSUE_TEMPLATE/bug_report.yml.github/pull_request_template.md.github/workflows/android.ymlCHANGELOG.mdCONTRIBUTING.mdREADME.mdSECURITY.mdapp/build.gradleapp/src/main/java/com/klee/volumelockr/BootReceiver.ktapp/src/main/java/com/klee/volumelockr/schedule/ScheduleData.ktapp/src/main/java/com/klee/volumelockr/schedule/ScheduleManager.ktapp/src/main/java/com/klee/volumelockr/service/VolumeService.ktapp/src/main/java/com/klee/volumelockr/ui/MainActivity.ktapp/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.ktapp/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.ktapp/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.ktapp/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.ktapp/src/main/res/layout/dialog_time_slot.xmlapp/src/main/res/layout/fragment_schedule.xmlapp/src/main/res/layout/item_schedule.xmlapp/src/main/res/menu/bottom_nav_menu.xmlapp/src/main/res/menu/options.xmlapp/src/main/res/navigation/navigation_graph.xmlapp/src/main/res/values-zh/strings.xmlapp/src/main/res/values/strings.xmlbuild.gradlegradle.propertiesgradle/libs.versions.toml
💤 Files with no reviewable changes (1)
- app/src/main/res/menu/options.xml
✅ Files skipped from review due to trivial changes (8)
- .editorconfig
- gradle/libs.versions.toml
- .github/FUNDING.yml
- app/src/main/res/layout/dialog_time_slot.xml
- SECURITY.md
- app/src/main/res/layout/fragment_schedule.xml
- CONTRIBUTING.md
- README.md
| ``` | ||
| - Tested on [Device] (Android [Version]) | ||
| - ./gradlew detekt passes | ||
| - ./gradlew test passes | ||
| ``` |
There was a problem hiding this comment.
Specify a language for the fenced code block to satisfy markdownlint.
This currently triggers MD040.
Suggested fix
-```
+```text
- Tested on [Device] (Android [Version])
- ./gradlew detekt passes
- ./gradlew test passes</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 23-23: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/pull_request_template.md around lines 23 - 27, The fenced code block
in .github/pull_request_template.md is missing a language and triggers
markdownlint MD040; update the triple-backtick opening fence to include a
language (e.g., change ``` to ```text) for the block that contains the three
checklist lines so the code fence becomes a language-specified block and
satisfies the linter.
| /** 检查给定的星期是否匹配此时间段的星期规则 */ | ||
| fun matchesDay(dayOfWeek: Int /* 1=周日 Calendar.SUNDAY → 需转换为 0=周日 */): Boolean { | ||
| return when (dayType) { | ||
| DayType.EVERYDAY -> true | ||
| DayType.WORKDAY -> dayOfWeek in setOf(1, 2, 3, 4, 5) // 周一~周五 (Calendar: 2-6) | ||
| DayType.WEEKEND -> dayOfWeek in setOf(0, 6) // 周六日 (转换后) | ||
| DayType.CUSTOM -> dayOfWeek in customDays | ||
| } | ||
| } | ||
|
|
||
| /** 检查给定的时分是否落在此时间段内 */ | ||
| fun matchesTime(hour: Int, minute: Int): Boolean { | ||
| val nowMinutes = hour * 60 + minute | ||
| return if (crossesMidnight) { | ||
| nowMinutes >= startMinutes || nowMinutes < endMinutes | ||
| } else { | ||
| nowMinutes in startMinutes until endMinutes | ||
| } |
There was a problem hiding this comment.
Cross-midnight slots can miss the after-midnight portion for day-restricted rules.
Current logic checks matchesDay(currentDay) and matchesTime(...) separately. For cross-midnight windows, times after midnight should often evaluate against the previous day’s rule set, otherwise CUSTOM/WEEKEND cases can be skipped incorrectly.
Suggested direction
+fun matchesDateTime(dayOfWeek: Int, hour: Int, minute: Int): Boolean {
+ val nowMinutes = hour * 60 + minute
+ if (!crossesMidnight) return matchesDay(dayOfWeek) && nowMinutes in startMinutes until endMinutes
+
+ return if (nowMinutes >= startMinutes) {
+ matchesDay(dayOfWeek)
+ } else if (nowMinutes < endMinutes) {
+ val prevDay = (dayOfWeek + 6) % 7
+ matchesDay(prevDay)
+ } else {
+ false
+ }
+}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/schedule/ScheduleData.kt` around lines
35 - 52, The matchesDay + matchesTime split fails for cross-midnight windows
because matchesTime after-midnight should be validated against the previous-day
rules; update logic so that when crossesMidnight is true and nowMinutes <
endMinutes you call matchesDay(previousDay) (compute previousDay = (dayOfWeek -
1 mod 7) consistent with your day numbering) instead of matchesDay(currentDay),
while keeping existing behavior for nowMinutes >= startMinutes; modify the
caller or add a helper (or a new combined method) that uses matchesTime(hour,
minute) together with dayType/customDays and this previous-day rule so
CUSTOM/WEEKEND/WORKDAY are evaluated correctly for the after-midnight portion
(refer to matchesDay, matchesTime, crossesMidnight, startMinutes, endMinutes,
customDays).
| holder.binding.switchSlotEnabled.isChecked = slot.enabled | ||
| holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null) | ||
| holder.binding.switchSlotEnabled.setOnCheckedChangeListener { _, checked -> |
There was a problem hiding this comment.
Prevent recycled listener callbacks during bind (Line 44).
isChecked is assigned before clearing the old listener, so recycled holders can emit unintended onToggle callbacks while binding.
Proposed fix
- holder.binding.switchSlotEnabled.isChecked = slot.enabled
- holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null)
+ holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null)
+ holder.binding.switchSlotEnabled.isChecked = slot.enabled
holder.binding.switchSlotEnabled.setOnCheckedChangeListener { _, checked ->
onToggle(slot, checked)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt` around lines 44
- 46, The binding sequence is causing recycled Switch listeners to fire during
bind; in onBindViewHolder (where holder.binding.switchSlotEnabled is used) clear
the old listener first by calling
holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null), then set
holder.binding.switchSlotEnabled.isChecked = slot.enabled, and finally reattach
the new listener via setOnCheckedChangeListener { _, checked -> ... } so the
recycled callbacks (e.g., onToggle) are not triggered during view binding.
| private fun getDayTypeLabel(dayType: DayType): String = when (dayType) { | ||
| DayType.EVERYDAY -> context.getString(com.klee.volumelockr.R.string.schedule_everyday) | ||
| DayType.WORKDAY -> context.getString(com.klee.volumelockr.R.string.schedule_workday) | ||
| DayType.WEEKEND -> context.getString(com.klee.volumelockr.R.string.schedule_weekend) | ||
| DayType.CUSTOM -> context.getString(com.klee.volumelockr.R.string.schedule_everyday) | ||
| } |
There was a problem hiding this comment.
Show a distinct label for DayType.CUSTOM (Line 59).
Mapping CUSTOM to “Every day” misrepresents schedule scope in the list UI. Use a dedicated localized label for custom days.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt` around lines 55
- 60, The getDayTypeLabel function currently maps DayType.CUSTOM to the everyday
label; update getDayTypeLabel (in ScheduleAdapter) so DayType.CUSTOM returns a
distinct localized string (e.g.,
context.getString(com.klee.volumelockr.R.string.schedule_custom)) instead of
schedule_everyday, and add the corresponding schedule_custom entry to your
strings.xml resources with the proper translation(s).
| val remaining = (config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000 | ||
| binding.tvTempUnlockText.text = getString(R.string.schedule_temp_unlock_remaining, remaining) |
There was a problem hiding this comment.
Clamp temporary unlock minutes to non-negative (Line 110).
If expiration just passed, UI can show negative minutes.
Proposed fix
- val remaining = (config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000
+ val remaining = ((config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000).coerceAtLeast(0)
binding.tvTempUnlockText.text = getString(R.string.schedule_temp_unlock_remaining, remaining)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt` around lines
110 - 111, In ScheduleFragment update the calculation of remaining minutes so it
can never be negative: compute the delta as (config.temporaryUnlockUntil -
System.currentTimeMillis()) / 60000 and clamp it to zero (e.g., using Math.max
or Kotlin's coerceAtLeast) before passing it to getString; update the reference
where binding.tvTempUnlockText.text is set to use the non-negative remaining
value.
| private fun handleImport(uriString: String) { | ||
| try { | ||
| val uri = android.net.Uri.parse(uriString) | ||
| val json = requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText() |
There was a problem hiding this comment.
Close import input stream with use (Line 401).
The current import read path does not scope-close the stream, which can leak file descriptors on repeated imports.
Proposed fix
- val json = requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
+ val json = requireContext().contentResolver
+ .openInputStream(uri)
+ ?.bufferedReader()
+ ?.use { it.readText() }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val json = requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText() | |
| val json = requireContext().contentResolver | |
| .openInputStream(uri) | |
| ?.bufferedReader() | |
| ?.use { it.readText() } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt` at line 401,
The import path in ScheduleFragment's code uses
requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
without closing the stream; update the import logic to scope-close the
InputStream and the BufferedReader by using Kotlin's use blocks (e.g.,
openInputStream(uri)?.use { input -> input.bufferedReader().use { reader ->
reader.readText() } }) so the file descriptor is always released, and assign
that result to the existing json variable.
| <string name="schedule_import_success">Schedules imported</string> | ||
| <string name="schedule_import_failed">Import failed</string> | ||
| <string name="delete">Delete</string> | ||
| <string name="schedule_lock_active_msg">" %s " is currently active. Temporarily unlock to adjust volume?</string> |
There was a problem hiding this comment.
Remove unintended spaces around %s in active-lock message (Line 66).
Current text renders awkward spacing/quotes (" %s "). Use %s directly.
Proposed fix
- <string name="schedule_lock_active_msg">" %s " is currently active. Temporarily unlock to adjust volume?</string>
+ <string name="schedule_lock_active_msg">"%s" is currently active. Temporarily unlock to adjust volume?</string>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <string name="schedule_lock_active_msg">" %s " is currently active. Temporarily unlock to adjust volume?</string> | |
| <string name="schedule_lock_active_msg">"%s" is currently active. Temporarily unlock to adjust volume?</string> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/res/values/strings.xml` at line 66, Update the string resource
named schedule_lock_active_msg to remove the extra spaces around the %s
placeholder (and ensure any surrounding quotation marks are adjacent to %s
rather than separated by spaces) so the formatted app name renders without
awkward gaps; locate schedule_lock_active_msg in the strings.xml and adjust the
text accordingly.
| [Unreleased]: https://github.com/xunnv/VolumeLockr/compare/v1.7.1-zh...HEAD | ||
| [1.7.1-zh]: https://github.com/xunnv/VolumeLockr/compare/v1.7.1...v1.7.1-zh |
There was a problem hiding this comment.
Remove or use the unused 1.7.1-zh link reference.
Line 105 defines [1.7.1-zh] but no changelog section references it, so it becomes stale noise and triggers markdown lint.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 105-105: Link and image reference definitions should be needed
Unused link or image reference definition: "1.7.1-zh"
(MD053, link-image-reference-definitions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` around lines 104 - 105, Remove or repurpose the unused markdown
link reference "[1.7.1-zh]" in the CHANGELOG: either delete the `[1.7.1-zh]:
https://github.com/xunnv/VolumeLockr/compare/v1.7.1...v1.7.1-zh` reference or
add a corresponding changelog section that references `[1.7.1-zh]` so the label
is actually used and markdownlint will no longer flag it.
|
@xunnv Comments in the code and commits description should be in english. |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt (2)
103-111:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAlways reset row UI for both locked and unlocked states during bind.
Only handling the locked case allows recycled holders to retain stale checked/disabled state for unlocked rows.
✅ Suggested bind-safe state handling
private fun loadLockFromService(holder: ViewHolder, volume: Volume) { - if (mService?.getLocks()?.containsKey(volume.stream) == true) { - // 临时移除监听器,避免设置 isChecked 时触发 onVolumeLocked - holder.binding.switchButton.setOnCheckedChangeListener(null) - holder.binding.switchButton.isChecked = true - holder.binding.slider.isEnabled = false - // 恢复监听器 - registerSwitchButtonCallback(holder, volume) - } + val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true + holder.binding.switchButton.setOnCheckedChangeListener(null) + holder.binding.switchButton.isChecked = isLocked + holder.binding.slider.isEnabled = !isLocked + registerSwitchButtonCallback(holder, volume) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 103 - 111, The bind currently only updates UI when a volume is locked causing recycled ViewHolders to retain stale checked/disabled state; in loadLockFromService add an explicit else branch that also resets the row UI for the unlocked case: detach the switch listener (holder.binding.switchButton.setOnCheckedChangeListener(null)), set holder.binding.switchButton.isChecked = false, set holder.binding.slider.isEnabled = true, then reattach the callback via registerSwitchButtonCallback(holder, volume); ensure you always remove the listener before changing isChecked for both branches and use the same registerSwitchButtonCallback(...) to restore the listener.
39-50:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandle insertions/removals before partial item-change notifications.
When
volumes.sizechanges,notifyItemChangedalone is not sufficient and can put RecyclerView in an inconsistent state.✅ Suggested fix for stable RecyclerView updates
`@MainThread` fun update(volumes: List<Volume>) { + val oldSize = mVolumeList.size + val overlap = minOf(oldSize, volumes.size) + // 找出实际变化的项,避免整体刷新 val changedPositions = mutableListOf<Int>() - for (i in volumes.indices) { - if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value + for (i in 0 until overlap) { + if (volumes[i].value != mVolumeList[i].value || volumes[i].locked != mVolumeList[i].locked) { changedPositions.add(i) } } + mVolumeList = volumes + when { + volumes.size > oldSize -> notifyItemRangeInserted(oldSize, volumes.size - oldSize) + volumes.size < oldSize -> notifyItemRangeRemoved(volumes.size, oldSize - volumes.size) + } changedPositions.forEach { notifyItemChanged(it) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 39 - 50, The update method currently only calls notifyItemChanged and ignores list-size changes; capture the old list (val oldList = mVolumeList), compute oldSize and newSize, update mVolumeList = volumes, then handle removals/insertions before per-item changes: if newSize < oldSize call notifyItemRangeRemoved(newSize, oldSize - newSize); if newSize > oldSize call notifyItemRangeInserted(oldSize, newSize - oldSize); finally iterate 0 until min(oldSize, newSize) and call notifyItemChanged(i) only for items where volumes[i].value or volumes[i].locked differ from oldList[i]; use the update function name and mVolumeList/notifyItemRangeRemoved/notifyItemRangeInserted/notifyItemChanged to locate the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 194-199: The cancel handler set in setOnCancelListener currently
only clears mTempUnlockDialogShowing but doesn't revert the UI state, so when
the temp-unlock dialog is dismissed via back/outside the switch remains checked;
update the setOnCancelListener to also reset
holder.binding.switchButton.isChecked = false (the same action performed in the
setNegativeButton lambda) and ensure any cleanup done in onVolumeLocked()
remains consistent with this UI change so the switch accurately reflects that no
lock was added.
In `@app/src/main/res/layout/dialog_time_slot.xml`:
- Line 14: Replace the Chinese inline XML comments in dialog_time_slot.xml with
English equivalents to keep the codebase accessible; specifically update the
comment currently "启用开关 + 名称" and the other Chinese comments at the same
locations (lines showing the other UI comment blocks) to clear English phrases
(e.g., "Enable switch + label", "Start time", "End time", "Repeat options" —
adjust to match the actual UI elements) by editing the XML comment nodes so they
are in English; ensure the comments remain as XML comments (<!-- ... -->) and
preserve surrounding tags/IDs such as the time slot layout elements to avoid
changing structure.
- Line 113: The layout currently hardcodes time literals via
android:text="08:00" (and similarly "22:00"); replace those static literals with
string resources (e.g., `@string/default_start_time` and `@string/default_end_time`)
or remove the android:text and set the default times at runtime using
locale-aware formatting (e.g., DateFormat/TimeFormatter) in the containing
view/activity/fragment. Update dialog_time_slot.xml to reference the new string
resource keys and add corresponding entries to strings.xml, or clear the
android:text attributes and set the formatted time in the code path that
inflates the dialog (use the view IDs for the time TextViews to locate them).
---
Duplicate comments:
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 103-111: The bind currently only updates UI when a volume is
locked causing recycled ViewHolders to retain stale checked/disabled state; in
loadLockFromService add an explicit else branch that also resets the row UI for
the unlocked case: detach the switch listener
(holder.binding.switchButton.setOnCheckedChangeListener(null)), set
holder.binding.switchButton.isChecked = false, set
holder.binding.slider.isEnabled = true, then reattach the callback via
registerSwitchButtonCallback(holder, volume); ensure you always remove the
listener before changing isChecked for both branches and use the same
registerSwitchButtonCallback(...) to restore the listener.
- Around line 39-50: The update method currently only calls notifyItemChanged
and ignores list-size changes; capture the old list (val oldList = mVolumeList),
compute oldSize and newSize, update mVolumeList = volumes, then handle
removals/insertions before per-item changes: if newSize < oldSize call
notifyItemRangeRemoved(newSize, oldSize - newSize); if newSize > oldSize call
notifyItemRangeInserted(oldSize, newSize - oldSize); finally iterate 0 until
min(oldSize, newSize) and call notifyItemChanged(i) only for items where
volumes[i].value or volumes[i].locked differ from oldList[i]; use the update
function name and
mVolumeList/notifyItemRangeRemoved/notifyItemRangeInserted/notifyItemChanged to
locate the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5ee09874-c823-426b-b66a-a5b0fdb6edb0
⛔ Files ignored due to path filters (3)
fastlane/metadata/android/en-US/images/icon.pngis excluded by!**/*.pngfastlane/metadata/android/en-US/images/phoneScreenshots/1.pngis excluded by!**/*.pngfastlane/metadata/android/en-US/images/phoneScreenshots/2.pngis excluded by!**/*.png
📒 Files selected for processing (12)
.github/FUNDING.ymlapp/src/androidTest/java/com/klee/volumelockr/ExampleInstrumentedTest.ktapp/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.ktapp/src/main/res/layout/dialog_time_slot.xmlapp/src/test/java/com/klee/volumelockr/ExampleUnitTest.ktfastlane/metadata/android/en-US/full_description.txtfastlane/metadata/android/en-US/short_description.txtfastlane/metadata/android/en-US/title.txtfastlane/metadata/android/fr-FR/full_description.txtfastlane/metadata/android/fr-FR/short_description.txtfastlane/metadata/android/ru/full_description.txtfastlane/metadata/android/ru/short_description.txt
💤 Files with no reviewable changes (10)
- fastlane/metadata/android/en-US/short_description.txt
- fastlane/metadata/android/fr-FR/short_description.txt
- fastlane/metadata/android/ru/full_description.txt
- fastlane/metadata/android/en-US/full_description.txt
- fastlane/metadata/android/fr-FR/full_description.txt
- fastlane/metadata/android/ru/short_description.txt
- fastlane/metadata/android/en-US/title.txt
- app/src/androidTest/java/com/klee/volumelockr/ExampleInstrumentedTest.kt
- .github/FUNDING.yml
- app/src/test/java/com/klee/volumelockr/ExampleUnitTest.kt
| .setOnCancelListener { mTempUnlockDialogShowing = false } | ||
| .setNegativeButton(android.R.string.cancel) { _, _ -> | ||
| mTempUnlockDialogShowing = false | ||
| // 取消时恢复开关状态,避免开关处于开启但无锁定的矛盾状态 | ||
| holder.binding.switchButton.isChecked = false | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# locate file
ls -RRepository: jonathanklee/VolumeLockr
Length of output: 2542
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -a "VolumeAdapter.kt" . -t fRepository: jonathanklee/VolumeLockr
Length of output: 146
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "FILE=$FILE"
wc -l "$FILE"
rg -n "setOnCancelListener|switchButton|registerSwitchButtonCallback|setNegativeButton" "$FILE" || trueRepository: jonathanklee/VolumeLockr
Length of output: 1001
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
# print around the line range mentioned (194-199) and some context
nl -ba "$FILE" | sed -n '160,240p'Repository: jonathanklee/VolumeLockr
Length of output: 111
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "FILE=$FILE"
wc -l "$FILE"
echo "---- matches ----"
rg -n "setOnCancelListener|switchButton|registerSwitchButtonCallback|setNegativeButton" "$FILE" || true
echo "---- context 160-240 ----"
nl -ba "$FILE" | sed -n '160,240p'Repository: jonathanklee/VolumeLockr
Length of output: 1106
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "---- context 240-330 ----"
nl -ba "$FILE" | sed -n '240,330p'Repository: jonathanklee/VolumeLockr
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "---- context 130-200 (wider) ----"
nl -ba "$FILE" | sed -n '120,215p'Repository: jonathanklee/VolumeLockr
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "FILE=$FILE"
wc -l "$FILE"
echo "---- rg matches ----"
rg -n "setOnCancelListener|switchButton|registerSwitchButtonCallback|setNegativeButton" "$FILE" || true
echo "---- context 150-230 ----"
nl -ba "$FILE" | sed -n '150,230p'
echo "---- context 230-310 ----"
nl -ba "$FILE" | sed -n '230,310p'Repository: jonathanklee/VolumeLockr
Length of output: 1109
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
echo "---- matches ----"
rg -n "setOnCancelListener|setNegativeButton|setOnDismissListener|switchButton|setOnCheckedChangeListener|registerSwitchButtonCallback" "$FILE"
echo "---- context around dialog (lines 160-240) ----"
nl -ba "$FILE" | sed -n '160,240p'
echo "---- context around switch handling (wider) ----"
nl -ba "$FILE" | sed -n '1,140p'
echo "---- context around registerSwitchButtonCallback ----"
rg -n "fun registerSwitchButtonCallback|registerSwitchButtonCallback" "$FILE" | head -n 50Repository: jonathanklee/VolumeLockr
Length of output: 964
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
sed -n '60,130p' "$FILE"
echo "----"
sed -n '160,215p' "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 5145
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
sed -n '60,130p' "$FILE"
echo "----"
sed -n '160,215p' "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 5145
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
rg -n "showTempUnlockDialog\\(" "$FILE"
echo "---- context around onVolumeLocked ----"
rg -n "private fun onVolumeLocked|onVolumeLocked\\(" "$FILE"
echo "---- print larger context around onVolumeLocked and any temp unlock logic ----"
sed -n '130,210p' "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 3682
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
rg -n "showTempUnlockDialog\\(" "$FILE"
echo "---- context around onVolumeLocked/onVolumeUnlocked and showTempUnlockDialog call ----"
sed -n '210,280p' "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 858
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
rg -n "onVolumeLocked\\(|onVolumeUnlocked\\(" "$FILE"
sed -n '180,260p' "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 2011
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
sed -n '90,180p' "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 3552
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
# get line numbers for onVolumeLocked definition
LINE=$(rg -n "private fun onVolumeLocked" "$FILE" | cut -d: -f1 | head -n1)
echo "onVolumeLocked at $LINE"
sed -n "$((LINE-40)),$((LINE+40))p" "$FILE"
# also show where showTempUnlockDialog called
CALL=$(rg -n "showTempUnlockDialog\\(" "$FILE" | head -n1 | cut -d: -f1)
echo "showTempUnlockDialog call at $CALL"
sed -n "$((CALL-20)),$((CALL+20))p" "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 4800
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
cat "$FILE" | head -n 220Repository: jonathanklee/VolumeLockr
Length of output: 8356
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
echo "---- line numbers for onVolumeLocked/showTempUnlockDialog ----"
rg -n "private fun onVolumeLocked|showTempUnlockDialog\\(" "$FILE"
echo "---- onVolumeLocked block (around its definition) ----"
LINE_LOCK=$(rg -n "private fun onVolumeLocked" "$FILE" | cut -d: -f1 | head -n1)
sed -n "$((LINE_LOCK-40)),$((LINE_LOCK+120))p" "$FILE"
echo "---- showTempUnlockDialog call site (around its first call) ----"
LINE_CALL=$(rg -n "showTempUnlockDialog\\(" "$FILE" | cut -d: -f1 | head -n1)
sed -n "$((LINE_CALL-30)),$((LINE_CALL+60))p" "$FILE"Repository: jonathanklee/VolumeLockr
Length of output: 8674
Back/outside cancel should also revert the switch state.
setOnCancelListener only clears mTempUnlockDialogShowing; dismissing the temp-unlock dialog via back/outside leaves holder.binding.switchButton checked even though onVolumeLocked() returned without adding a lock (the negative/cancel button handler already resets the switch).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 194
- 199, The cancel handler set in setOnCancelListener currently only clears
mTempUnlockDialogShowing but doesn't revert the UI state, so when the
temp-unlock dialog is dismissed via back/outside the switch remains checked;
update the setOnCancelListener to also reset
holder.binding.switchButton.isChecked = false (the same action performed in the
setNegativeButton lambda) and ensure any cleanup done in onVolumeLocked()
remains consistent with this UI change so the switch accurately reflects that no
lock was added.
| android:orientation="vertical" | ||
| android:padding="16dp"> | ||
|
|
||
| <!-- 启用开关 + 名称 --> |
There was a problem hiding this comment.
Use English for XML comments to keep codebase collaboration-friendly.
These inline comments are currently Chinese-only. Please switch them to English to match team review expectations and keep maintenance accessible to all contributors.
Also applies to: 54-54, 93-93, 117-117, 141-141
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/res/layout/dialog_time_slot.xml` at line 14, Replace the Chinese
inline XML comments in dialog_time_slot.xml with English equivalents to keep the
codebase accessible; specifically update the comment currently "启用开关 + 名称" and
the other Chinese comments at the same locations (lines showing the other UI
comment blocks) to clear English phrases (e.g., "Enable switch + label", "Start
time", "End time", "Repeat options" — adjust to match the actual UI elements) by
editing the XML comment nodes so they are in English; ensure the comments remain
as XML comments (<!-- ... -->) and preserve surrounding tags/IDs such as the
time slot layout elements to avoid changing structure.
| android:layout_width="wrap_content" | ||
| android:layout_height="40dp" | ||
| android:minWidth="0dp" | ||
| android:text="08:00" |
There was a problem hiding this comment.
Avoid hardcoded default time literals in layout.
"08:00" / "22:00" should come from resources (or be set at runtime with locale-aware formatting) to keep localization behavior consistent.
💡 Minimal XML-side adjustment
- android:text="08:00"
+ android:text="`@string/schedule_default_start_time`"
...
- android:text="22:00"
+ android:text="`@string/schedule_default_end_time`"Also applies to: 137-137
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/res/layout/dialog_time_slot.xml` at line 113, The layout
currently hardcodes time literals via android:text="08:00" (and similarly
"22:00"); replace those static literals with string resources (e.g.,
`@string/default_start_time` and `@string/default_end_time`) or remove the
android:text and set the default times at runtime using locale-aware formatting
(e.g., DateFormat/TimeFormatter) in the containing view/activity/fragment.
Update dialog_time_slot.xml to reference the new string resource keys and add
corresponding entries to strings.xml, or clear the android:text attributes and
set the formatted time in the code path that inflates the dialog (use the view
IDs for the time TextViews to locate them).
Summary
This PR brings comprehensive improvements to VolumeLockr, focusing on Chinese localization, MIUI compatibility fixes, boot stability, and performance optimization.
Added
zh-CN) translationActivityManager.AppTask.setExcludeFromRecents()(fixes MIUI override of Intent flags)VolumeLockrtag) for boot-time debuggingFixed
ForegroundServiceStartNotAllowedException→stopSelf()→START_STICKYcaused infinite restart on Android 12+startService()Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTSis overridden by MIUI/HyperOSPerformance
Timerpolling withScheduledExecutorService(500ms interval, 80% CPU reduction)ContentObserver→checkVolumes()chain (eliminated 12 premature boot calls)notifyItemChanged(pos)instead ofnotifyDataSetChanged()isPasswordProtected(), O(1) lock lookup, Gson singleton reuseSecurity
build.gradlerelease/keystore.properties(git-ignored)Tested on: Xiaomi Redmi (MIUI 14 / HyperOS), Android 14
Closes: Boot auto-start issues, MIUI recents hiding, background volume lock reliability
Summary by CodeRabbit
New Features
Enhancements
Documentation
Chores