feat(kids flavor): opt-in parental controls for family Signal use#747
feat(kids flavor): opt-in parental controls for family Signal use#747avacar wants to merge 10 commits into
Conversation
ec38f08 to
cf48d44
Compare
Adds a 'kids' flavor to the distribution dimension. The flavor enables a parental mode build that restricts children to pre-approved contacts. No changes to the standard prod/staging flavors.
- Create ParentalControlValues.kt extending SignalStoreValues to store: * parentalModeEnabled (defaults to true) * parentPinHash (empty string = no PIN set) * PIN salt (auto-generated random 16-byte salt, stored locally) * allowedThreadIds (Set of conversation thread IDs) - computePinHash() helper using SHA-256(salt + pin) - Register in SignalStore.kt (init, onFirstEverAppLaunch, accessor) - Create comprehensive unit tests covering defaults, PIN hashing, salt generation, thread ID storage - No backup inclusion (parental config is device-local) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fixed ParentalControlValuesTest: wrap KeyValueDataSet in an in-memory KeyValuePersistentStorage (upstream API change) - Fixed ParentalControlValuesTest: replace containsExactly with isEqualTo(setOf(...)) for Set<Long> assertion - Added JDK path properties to gradle.properties
When parental mode is enabled, ConversationListViewModel.conversationsState now filters out any THREAD-type conversation not in the parent-approved allowedThreadIds set. Non-THREAD items (headers, footers) always pass through to preserve list structure. ParentalControlValues gains a settingsChanges PublishSubject that emits whenever parentalModeEnabled or allowedThreadIds changes; the ViewModel subscribes and invalidates the paging controller so the filtered list updates reactively without touching any database queries. 14 unit tests added across ParentalControlValuesTest and the new ConversationListParentalFilterTest (10 tests for filter logic, 2 for dynamic settingsChanges emission, 2 previously existing). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ind PIN Phase 3 — Block New Conversations & Calls: - Hide compose and call FABs in parental mode (MainFloatingActionButtons) - Safety-net finish() in NewConversationActivity and NewCallActivity - Suppress incoming 1:1 calls from non-allowed threads (IncomingCallActionProcessor) - Suppress incoming group call rings from non-allowed threads (IncomingGroupCallActionProcessor) - isThreadCallAllowed() helper in ParentalControlValues - 7 tests in ParentalCallGuardTest + 5 more in ParentalControlValuesTest (27 total) Phase 4 — Group Invite PIN Gate: - Invites hidden from children; parents manage via PIN-gated overflow menu item - verifyPin() and addAllowedThreadId() added to ParentalControlValues - GROUP_V2_INVITE accept UI suppressed in ConversationFragment - Group-link join blocked in CommunicationActions (toast shown) - ParentalPinDialog: reusable PIN entry dialog - PendingGroupInvitesViewModel + PendingGroupInvitesFragment (BottomSheet) - "Pending group invites" item in MainToolbar, wired in MainActivity - Accepting invite auto-adds thread to allowedThreadIds → Phase 2 filter shows it - 7 tests in ParentalInviteGuardTest + 5 more in ParentalControlValuesTest (32 total) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NotificationState.filterThreads(allowedThreadIds): filters conversations list using data class copy(); preserves mute/profile filtered message side-lists - DefaultMessageNotifier.updateNotification(): applies filter immediately after constructNotificationState(); existing isEmpty early-exit silences all notifications when no threads are allowed - NotificationStateParentalFilterTest: 5 pure unit tests (no Robolectric needed) covering allowed retained, disallowed removed, mixed, empty set, side-lists - 37 total parental-control tests green; assembleProdKidsDebug BUILD SUCCESSFUL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract buildNavEntries() helper in MainNavigation.kt; both MainNavigationBar and MainNavigationRail now filter STORIES when parentalModeEnabled is true - AppSettingsFragment: hide Account, Linked Devices, Donate to Signal rows (and their trailing divider) behind !parentalModeEnabled guard - PrivacySettingsFragment: hide Privacy > Advanced clickPref behind !parentalModeEnabled guard - Add ParentalNavigationFilterTest (4 tests); 41 parental tests total, all green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Parent Controls" entry to the main overflow menu (visible when parental mode is enabled). Tapping it requires PIN entry, then opens ParentalControlActivity with three sections: - Master toggle for parental mode - Per-thread switch list to manage allowedThreadIds - Change PIN (two-field dialog with length validation) Fresh-install path: if no PIN is set, the activity opens directly with a PIN-setup dialog instead of the verification dialog. New files: ParentalControlActivity, ParentalControlViewModel, activity_parental_control.xml, item_parental_thread.xml, ParentalControlViewModelTest (6 tests; 62 total parental tests green). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntal guards - Remove applicationIdSuffix from kids flavor (caused install conflicts) - Fix New Group menu item visibility in parental mode - Fix Create a call link row appearing in Calls tab via paging consistency - Add parental guards to ConversationSettingsActivity and ConversationOptionsMenu - Tighten MainToolbar/MainNavigationRepository parental filtering - Update project_status.md with 2026-05-03 session notes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cf48d44 to
d02c862
Compare
469424f to
bf57ae5
Compare
|
This is great! I happen to be aware of parents who will not let their children use anything other than a landline or dumb phone without images if that exists because they are concerned about their child seeing genitalia, gore, etc. from their school friends who don't have a locked down device. I propose these remedies: allow disabling receiving images and videos, video calls, and eventually use an AI model to detect nudity and gore and block it. This is provided as part of AICore (app included on Google Mobile Services devices) and does function locally, but an open source alternative could be provided in the future and since it will likely use significant storage it should be provided as an addon pack via another app (in the same way as icon packs) which can be updated by Molly to avoid dynamic code loading or wasting storage for users who don't want this. Separately, allow disabling sending images and videos to prevent them from sending inappropriate images. It probably makes sense for the settings for sending/receiving images/videos and video calls to be per-contact. |
|
And possibly somewhat controversially, deleted messages should be able to be sent to a quarantine so that parents can review them later. I know this might upset some people but I dare deeply for children and this would not impact the privacy, security, freedom or anything else of adults and would allow concerned parents to choose a safe option where if their children make a mistake, their messages are not stored in a central database, contrary to mainstream "kid friendly" messengers or SMS monitoring tools. |
Summary
This PR adds an opt-in
kidsGradle build flavor to Molly that enables parental controlsfor families who want to use Signal-compatible messaging with young children.
All changes are behind the
kidsflavor flag. The existingprodandstagingflavors arecompletely unaffected — this is purely additive.
Motivation
Families with young children often want a secure, private messaging channel with grandparents,
relatives, and family friends. Signal is an excellent technical foundation for this, but it has
no concept of a "restricted" child account. The child can message anyone, see all contacts, and
change account settings.
This fork adds a PIN-gated parental layer so parents can:
Registration uses free Google Voice numbers so children never expose personal phone numbers.
Communication happens via Signal usernames, requiring no app changes.
What's in this PR
kidsGradle build flavor +parentalModeEnabledflagParentalControlValues.kt— PIN storage (SHA-256 + random salt) and allowed-thread listParentalControlActivity— PIN-gated parent settings panel on the child's device62 unit tests cover the parental-control layer. All pass on the current build.
Architecture — why this is safe to merge
New files only for parental logic. All parental UI and data live in:
app/src/main/java/.../keyvalue/ParentalControlValues.ktapp/src/main/java/.../parental/(Activity, ViewModel, dialogs)Minimal touch of existing files. Guard clauses are added at a handful of call sites:
No Signal protocol, crypto, database schema, or network code is modified.
Zero-conflict upstream tracking. This branch is based on Molly's current
mainand hasbeen kept in sync through multiple upstream releases with zero merge conflicts.
Opt-in at build time. A standard
./gradlew assembleProdReleaseproduces the normal MollyAPK. The parental controls only activate in a
kidsReleasebuild.Public fork
The full fork with history and documentation is at:
https://github.com/avacar/mollykids-android
Notes for reviewers
contribution (e.g. just the
kidsflavor scaffolding in Phase 0, with the rest maintaineddownstream).
ParentalControlActivityis intentionally a standaloneAppCompatActivityrather thana destination in
AppSettingsActivity's NavGraph, to avoid upstream merge conflicts in thesettings navigation graph.
SHA-256(salt + PIN)with a random 16-byte salt. No plaintext PIN isever persisted.