├── .github
├── FUNDING.yml
├── actions
│ └── common-setup
│ │ └── action.yml
├── assets
│ └── app_banner.png
├── release.yml
└── workflows
│ ├── code-checks.yml
│ ├── gradle-wrapper-validation.yml
│ └── release-tag.yml
├── .gitignore
├── .idea
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── CNAME
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── PRIVACY_POLICY.md
├── README.md
├── VERSION
├── _config.yml
├── _layouts
└── plain.html
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules-debug.pro
├── proguard
│ └── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── eu
│ │ └── darken
│ │ └── myperm
│ │ ├── ExampleInstrumentedTest.kt
│ │ └── HiltTestRunner.kt
│ ├── debug
│ ├── AndroidManifest.xml
│ └── java
│ │ └── eu
│ │ └── darken
│ │ └── myperm
│ │ └── HiltTestActivity.kt
│ ├── foss
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── eu
│ │ │ └── darken
│ │ │ └── myperm
│ │ │ └── common
│ │ │ ├── debug
│ │ │ ├── DebugModule.kt
│ │ │ ├── autoreporting
│ │ │ │ └── FossAutoReporting.kt
│ │ │ └── recording
│ │ │ │ ├── core
│ │ │ │ ├── ActualRecorderModule.kt
│ │ │ │ └── RecorderService.kt
│ │ │ │ └── ui
│ │ │ │ ├── RecorderActivity.kt
│ │ │ │ └── RecorderActivityVM.kt
│ │ │ └── upgrade
│ │ │ ├── UpgradeModule.kt
│ │ │ └── core
│ │ │ ├── FossCache.kt
│ │ │ ├── FossUpgrade.kt
│ │ │ └── UpgradeControlFoss.kt
│ └── res
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ └── values
│ │ └── strings.xml
│ ├── gplay
│ ├── java
│ │ └── eu
│ │ │ └── darken
│ │ │ └── myperm
│ │ │ └── common
│ │ │ ├── debug
│ │ │ ├── DebugModule.kt
│ │ │ ├── autoreport
│ │ │ │ └── GooglePlayReporting.kt
│ │ │ └── recording
│ │ │ │ └── core
│ │ │ │ └── NoopRecorderModule.kt
│ │ │ └── upgrade
│ │ │ ├── UpgradeModule.kt
│ │ │ └── core
│ │ │ ├── BillingCache.kt
│ │ │ ├── MyPermSku.kt
│ │ │ ├── UpgradeRepoGplay.kt
│ │ │ ├── client
│ │ │ ├── BillingClientConnection.kt
│ │ │ ├── BillingClientConnectionProvider.kt
│ │ │ ├── BillingClientExtensions.kt
│ │ │ ├── BillingException.kt
│ │ │ ├── BillingResultException.kt
│ │ │ └── GplayServiceUnavailableException.kt
│ │ │ └── data
│ │ │ ├── AvailableSku.kt
│ │ │ ├── BillingData.kt
│ │ │ ├── BillingDataRepo.kt
│ │ │ ├── PurchasedSku.kt
│ │ │ └── Sku.kt
│ └── res
│ │ └── values
│ │ └── strings.xml
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── eu
│ │ │ └── darken
│ │ │ └── myperm
│ │ │ ├── App.kt
│ │ │ ├── apps
│ │ │ ├── core
│ │ │ │ ├── AppRepo.kt
│ │ │ │ ├── AppRepoExtensions.kt
│ │ │ │ ├── AppStoreTool.kt
│ │ │ │ ├── PackageEventListener.kt
│ │ │ │ ├── PackageInfoExtensions.kt
│ │ │ │ ├── PackageManagerExtensions.kt
│ │ │ │ ├── Pkg.kt
│ │ │ │ ├── container
│ │ │ │ │ ├── BasePkg.kt
│ │ │ │ │ ├── PrimaryProfilePkg.kt
│ │ │ │ │ ├── SecondaryProfilePkg.kt
│ │ │ │ │ └── SecondaryUserPkg.kt
│ │ │ │ ├── features
│ │ │ │ │ ├── AccessibilityService.kt
│ │ │ │ │ ├── AppStore.kt
│ │ │ │ │ ├── BatteryOptimization.kt
│ │ │ │ │ ├── ExtraPermissionScanner.kt
│ │ │ │ │ ├── Installed.kt
│ │ │ │ │ ├── InstallerInfo.kt
│ │ │ │ │ ├── InternetAccess.kt
│ │ │ │ │ ├── PermissionState.kt
│ │ │ │ │ ├── ReadableApk.kt
│ │ │ │ │ ├── SecondaryPkg.kt
│ │ │ │ │ └── UsesPermission.kt
│ │ │ │ └── known
│ │ │ │ │ └── AKnownPkg.kt
│ │ │ └── ui
│ │ │ │ ├── details
│ │ │ │ ├── AppDetailsAdapter.kt
│ │ │ │ ├── AppDetailsEvents.kt
│ │ │ │ ├── AppDetailsFragment.kt
│ │ │ │ ├── AppDetailsFragmentVM.kt
│ │ │ │ └── items
│ │ │ │ │ ├── AppOverviewVH.kt
│ │ │ │ │ ├── AppSiblingsVH.kt
│ │ │ │ │ ├── AppTwinsVH.kt
│ │ │ │ │ ├── UnknownPermissionVH.kt
│ │ │ │ │ └── UsesPermissionVH.kt
│ │ │ │ └── list
│ │ │ │ ├── AppsAdapter.kt
│ │ │ │ ├── AppsEvents.kt
│ │ │ │ ├── AppsFilterOptions.kt
│ │ │ │ ├── AppsFragment.kt
│ │ │ │ ├── AppsFragmentVM.kt
│ │ │ │ ├── AppsSortOptions.kt
│ │ │ │ ├── FilterDialog.kt
│ │ │ │ ├── SortDialog.kt
│ │ │ │ └── apps
│ │ │ │ └── NormalAppVH.kt
│ │ │ ├── common
│ │ │ ├── AndroidVersionCodes.kt
│ │ │ ├── BuildConfigWrap.kt
│ │ │ ├── BuildWrap.kt
│ │ │ ├── ClipboardHelper.kt
│ │ │ ├── ContextExtensions.kt
│ │ │ ├── DividerItemDecorator.kt
│ │ │ ├── IPCFunnel.kt
│ │ │ ├── InstallId.kt
│ │ │ ├── LiveDataExtensions.kt
│ │ │ ├── LoadingBoxView.kt
│ │ │ ├── PrivacyPolicy.kt
│ │ │ ├── StringExtensions.kt
│ │ │ ├── UnitConverter.kt
│ │ │ ├── WebpageTool.kt
│ │ │ ├── coil
│ │ │ │ ├── AppIconFetcher.kt
│ │ │ │ ├── CoilExtensions.kt
│ │ │ │ ├── CoilModule.kt
│ │ │ │ ├── PermissionIconFetcher.kt
│ │ │ │ └── UsesPermissionIconFetcher.kt
│ │ │ ├── collections
│ │ │ │ ├── ByteArrayExtensions.kt
│ │ │ │ └── MapExtensions.kt
│ │ │ ├── compression
│ │ │ │ └── Zipper.kt
│ │ │ ├── coroutine
│ │ │ │ ├── AppCoroutineScope.kt
│ │ │ │ ├── CoroutineModule.kt
│ │ │ │ ├── DefaultDispatcherProvider.kt
│ │ │ │ └── DispatcherProvider.kt
│ │ │ ├── dagger
│ │ │ │ └── AndroidModule.kt
│ │ │ ├── debug
│ │ │ │ ├── Bugs.kt
│ │ │ │ ├── autoreport
│ │ │ │ │ ├── AutomaticBugReporter.kt
│ │ │ │ │ └── DebugSettings.kt
│ │ │ │ ├── logging
│ │ │ │ │ ├── FileLogger.kt
│ │ │ │ │ ├── LogCatLogger.kt
│ │ │ │ │ ├── LogExtensions.kt
│ │ │ │ │ └── Logging.kt
│ │ │ │ └── recording
│ │ │ │ │ └── core
│ │ │ │ │ ├── Recorder.kt
│ │ │ │ │ └── RecorderModule.kt
│ │ │ ├── dialog
│ │ │ │ └── BaseDialogBuilder.kt
│ │ │ ├── error
│ │ │ │ ├── ErrorDialog.kt
│ │ │ │ ├── ErrorEventSource.kt
│ │ │ │ ├── LocalizedError.kt
│ │ │ │ └── ThrowableExtensions.kt
│ │ │ ├── flow
│ │ │ │ ├── DynamicStateFlow.kt
│ │ │ │ ├── DynamicStateFlowExtensions.kt
│ │ │ │ ├── FlowCombineExtensions.kt
│ │ │ │ └── FlowExtensions.kt
│ │ │ ├── lists
│ │ │ │ ├── BaseAdapter.kt
│ │ │ │ ├── BindableVH.kt
│ │ │ │ ├── DataAdapter.kt
│ │ │ │ ├── ListItem.kt
│ │ │ │ ├── RecyclerViewExtensions.kt
│ │ │ │ ├── differ
│ │ │ │ │ ├── AsyncDiffer.kt
│ │ │ │ │ ├── AsyncDifferExtensions.kt
│ │ │ │ │ ├── DifferItem.kt
│ │ │ │ │ └── HasAsyncDiffer.kt
│ │ │ │ └── modular
│ │ │ │ │ ├── ModularAdapter.kt
│ │ │ │ │ └── mods
│ │ │ │ │ ├── ClickMod.kt
│ │ │ │ │ ├── DataBinderMod.kt
│ │ │ │ │ ├── SimpleVHCreatorMod.kt
│ │ │ │ │ ├── StableIdMod.kt
│ │ │ │ │ └── TypedVHCreatorMod.kt
│ │ │ ├── livedata
│ │ │ │ └── SingleLiveEvent.kt
│ │ │ ├── navigation
│ │ │ │ ├── FragmentExtensions.kt
│ │ │ │ ├── NavArgsExtensions.kt
│ │ │ │ ├── NavControllerExtensions.kt
│ │ │ │ ├── NavDestinationExtensions.kt
│ │ │ │ └── NavDirectionsExtensions.kt
│ │ │ ├── notifications
│ │ │ │ └── PendingIntentCompat.kt
│ │ │ ├── preferences
│ │ │ │ ├── FlowPreference.kt
│ │ │ │ ├── FlowPreferenceExtension.kt
│ │ │ │ ├── FlowPreferenceMoshiExtension.kt
│ │ │ │ ├── PreferenceStoreMapper.kt
│ │ │ │ ├── Settings.kt
│ │ │ │ └── SharedPreferenceExtensions.kt
│ │ │ ├── serialization
│ │ │ │ ├── MoshiExtensions.kt
│ │ │ │ └── SerializationModule.kt
│ │ │ ├── uix
│ │ │ │ ├── Activity2.kt
│ │ │ │ ├── BottomSheetDialogFragment2.kt
│ │ │ │ ├── Fragment2.kt
│ │ │ │ ├── Fragment3.kt
│ │ │ │ ├── PreferenceFragment2.kt
│ │ │ │ ├── Service2.kt
│ │ │ │ ├── ViewModel1.kt
│ │ │ │ ├── ViewModel2.kt
│ │ │ │ ├── ViewModel3.kt
│ │ │ │ └── ViewModelLazyKeyed.kt
│ │ │ ├── upgrade
│ │ │ │ ├── UpgradeRepo.kt
│ │ │ │ └── UpgradeRepoExtensions.kt
│ │ │ ├── viewbinding
│ │ │ │ └── ViewBindingExtensions.kt
│ │ │ └── worker
│ │ │ │ └── WorkerExtensions.kt
│ │ │ ├── main
│ │ │ └── ui
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainActivityVM.kt
│ │ │ │ ├── main
│ │ │ │ ├── MainFragment.kt
│ │ │ │ └── MainFragmentVM.kt
│ │ │ │ ├── onboarding
│ │ │ │ ├── OnboardingFragment.kt
│ │ │ │ └── OnboardingFragmentVM.kt
│ │ │ │ └── overview
│ │ │ │ ├── OverviewAdapter.kt
│ │ │ │ ├── OverviewFragment.kt
│ │ │ │ ├── OverviewFragmentVM.kt
│ │ │ │ ├── PkgCount.kt
│ │ │ │ └── items
│ │ │ │ ├── DeviceVH.kt
│ │ │ │ └── SummaryVH.kt
│ │ │ ├── permissions
│ │ │ ├── core
│ │ │ │ ├── Permission.kt
│ │ │ │ ├── PermissionGroup.kt
│ │ │ │ ├── PermissionInfoExtensions.kt
│ │ │ │ ├── PermissionRepo.kt
│ │ │ │ ├── PermissionRepoExtensions.kt
│ │ │ │ ├── container
│ │ │ │ │ ├── BasePermission.kt
│ │ │ │ │ ├── DeclaredPermission.kt
│ │ │ │ │ ├── ExtraPermission.kt
│ │ │ │ │ └── UnknownPermission.kt
│ │ │ │ ├── features
│ │ │ │ │ ├── PermissionAction.kt
│ │ │ │ │ └── PermissionTags.kt
│ │ │ │ └── known
│ │ │ │ │ ├── AExtraPerm.kt
│ │ │ │ │ ├── APerm.kt
│ │ │ │ │ └── APermGrp.kt
│ │ │ └── ui
│ │ │ │ ├── details
│ │ │ │ ├── PermissionDetailsAdapter.kt
│ │ │ │ ├── PermissionDetailsEvents.kt
│ │ │ │ ├── PermissionDetailsFragment.kt
│ │ │ │ ├── PermissionDetailsFragmentVM.kt
│ │ │ │ └── items
│ │ │ │ │ ├── AppDeclaringPermissionVH.kt
│ │ │ │ │ ├── AppRequestingPermissionVH.kt
│ │ │ │ │ └── PermissionOverviewVH.kt
│ │ │ │ └── list
│ │ │ │ ├── FilterDialog.kt
│ │ │ │ ├── PermissionListEvent.kt
│ │ │ │ ├── PermissionsAdapter.kt
│ │ │ │ ├── PermissionsFragment.kt
│ │ │ │ ├── PermissionsFragmentVM.kt
│ │ │ │ ├── PermsFilterOptions.kt
│ │ │ │ ├── PermsSortOptions.kt
│ │ │ │ ├── SortDialog.kt
│ │ │ │ ├── groups
│ │ │ │ ├── PermissionGroupItem.kt
│ │ │ │ └── PermissionGroupVH.kt
│ │ │ │ └── permissions
│ │ │ │ ├── DeclaredPermissionVH.kt
│ │ │ │ ├── ExtraPermissionVH.kt
│ │ │ │ ├── PermissionItem.kt
│ │ │ │ └── UnknownPermissionVH.kt
│ │ │ └── settings
│ │ │ ├── core
│ │ │ └── GeneralSettings.kt
│ │ │ └── ui
│ │ │ ├── SettingsFragment.kt
│ │ │ ├── SettingsFragmentVM.kt
│ │ │ ├── SettingsIndexFragment.kt
│ │ │ ├── acks
│ │ │ ├── AcknowledgementsFragment.kt
│ │ │ └── AcknowledgementsFragmentVM.kt
│ │ │ ├── general
│ │ │ ├── GeneralSettingsFragment.kt
│ │ │ └── GeneralSettingsFragmentVM.kt
│ │ │ └── support
│ │ │ ├── SupportFragment.kt
│ │ │ └── SupportFragmentVM.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xxxhdpi
│ │ └── ic_mascot.png
│ │ ├── drawable
│ │ ├── app_tag_box_bg.xml
│ │ ├── ic_access_background_location_24.xml
│ │ ├── ic_access_media_location_24.xml
│ │ ├── ic_access_notification_policy_24.xml
│ │ ├── ic_access_notifications_24.xml
│ │ ├── ic_access_to_media_only_24.xml
│ │ ├── ic_baseline_accessibility_new_24.xml
│ │ ├── ic_baseline_apps_24.xml
│ │ ├── ic_baseline_arrow_back_24.xml
│ │ ├── ic_baseline_battery_charging_full_24.xml
│ │ ├── ic_baseline_battery_unknown_24.xml
│ │ ├── ic_baseline_bluetooth_24.xml
│ │ ├── ic_baseline_bug_report_24.xml
│ │ ├── ic_baseline_calendar_today_24.xml
│ │ ├── ic_baseline_call_24.xml
│ │ ├── ic_baseline_camera_24.xml
│ │ ├── ic_baseline_check_circle_24.xml
│ │ ├── ic_baseline_coffee_24.xml
│ │ ├── ic_baseline_contact_support_24.xml
│ │ ├── ic_baseline_contacts_24.xml
│ │ ├── ic_baseline_directions_run_24.xml
│ │ ├── ic_baseline_edit_calendar_24.xml
│ │ ├── ic_baseline_expand_less_24.xml
│ │ ├── ic_baseline_expand_more_24.xml
│ │ ├── ic_baseline_filter_list_24.xml
│ │ ├── ic_baseline_gplay_24.xml
│ │ ├── ic_baseline_group_work_24.xml
│ │ ├── ic_baseline_heart_24.xml
│ │ ├── ic_baseline_id_24.xml
│ │ ├── ic_baseline_install_mobile_24.xml
│ │ ├── ic_baseline_internet_24.xml
│ │ ├── ic_baseline_local_phone_24.xml
│ │ ├── ic_baseline_location_on_24.xml
│ │ ├── ic_baseline_mic_24.xml
│ │ ├── ic_baseline_more_24.xml
│ │ ├── ic_baseline_phone_android_24.xml
│ │ ├── ic_baseline_photo_camera_24.xml
│ │ ├── ic_baseline_picture_in_picture_24.xml
│ │ ├── ic_baseline_privacy_tip_24.xml
│ │ ├── ic_baseline_question_mark_24.xml
│ │ ├── ic_baseline_refresh_24.xml
│ │ ├── ic_baseline_remove_circle_24.xml
│ │ ├── ic_baseline_sd_storage_24.xml
│ │ ├── ic_baseline_security_24.xml
│ │ ├── ic_baseline_sensors_24.xml
│ │ ├── ic_baseline_settings_24.xml
│ │ ├── ic_baseline_settings_applications_24.xml
│ │ ├── ic_baseline_shield_24.xml
│ │ ├── ic_baseline_signal_wifi_4_bar_24.xml
│ │ ├── ic_baseline_signal_wifi_connected_no_internet_4_24.xml
│ │ ├── ic_baseline_signal_wifi_statusbar_connected_no_internet_4_24.xml
│ │ ├── ic_baseline_sms_24.xml
│ │ ├── ic_baseline_sort_24.xml
│ │ ├── ic_baseline_source_24.xml
│ │ ├── ic_baseline_stars_24.xml
│ │ ├── ic_baseline_start_24.xml
│ │ ├── ic_baseline_user_24.xml
│ │ ├── ic_baseline_vibration_24.xml
│ │ ├── ic_baseline_work_24.xml
│ │ ├── ic_bluetooth_advertise_24.xml
│ │ ├── ic_body_sensors_24.xml
│ │ ├── ic_card_text_onsurface.xml
│ │ ├── ic_change_network_state_24.xml
│ │ ├── ic_change_wifi_state_24.xml
│ │ ├── ic_changelog_onsurface.xml
│ │ ├── ic_connectivity_24.xml
│ │ ├── ic_default_app_icon_24.xml
│ │ ├── ic_discord_onsurface.xml
│ │ ├── ic_email_onsurface.xml
│ │ ├── ic_foreground_service_24.xml
│ │ ├── ic_get_accounts_24.xml
│ │ ├── ic_github_onsurface.xml
│ │ ├── ic_heart.xml
│ │ ├── ic_id_onsurface.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_location_coarse_24.xml
│ │ ├── ic_location_fine_24.xml
│ │ ├── ic_manage_accounts_24.xml
│ │ ├── ic_manage_media_24.xml
│ │ ├── ic_modify_system_settings_24.xml
│ │ ├── ic_network_state_24.xml
│ │ ├── ic_nfc_24.xml
│ │ ├── ic_query_all_packages_24.xml
│ │ ├── ic_read_sync_settings_24.xml
│ │ ├── ic_reboot_permission_24.xml
│ │ ├── ic_schedule_exact_alarm_24.xml
│ │ ├── ic_spider_thread_onsurface.xml
│ │ ├── ic_system_alert_window_24.xml
│ │ ├── ic_unlimited_data_access_24.xml
│ │ ├── ic_usage_data_access_24.xml
│ │ ├── ic_wifi_state_24.xml
│ │ └── launch_screen.xml
│ │ ├── layout
│ │ ├── apps_details_fragment.xml
│ │ ├── apps_details_overview_item.xml
│ │ ├── apps_details_permission_unknown_item.xml
│ │ ├── apps_details_permission_uses_item.xml
│ │ ├── apps_details_siblings_item.xml
│ │ ├── apps_details_siblings_item_sibling.xml
│ │ ├── apps_details_twins_item.xml
│ │ ├── apps_details_twins_item_twin.xml
│ │ ├── apps_fragment.xml
│ │ ├── apps_normal_item.xml
│ │ ├── common_loading_box_view.xml
│ │ ├── debug_recording_activity.xml
│ │ ├── main_activity.xml
│ │ ├── main_fragment.xml
│ │ ├── onboarding_fragment.xml
│ │ ├── overview_fragment.xml
│ │ ├── overview_item_device.xml
│ │ ├── overview_item_summary.xml
│ │ ├── permissions_details_app_declaring_item.xml
│ │ ├── permissions_details_app_requesting_item.xml
│ │ ├── permissions_details_fragment.xml
│ │ ├── permissions_details_overview_item.xml
│ │ ├── permissions_fragment.xml
│ │ ├── permissions_list_declared_item.xml
│ │ ├── permissions_list_extra_item.xml
│ │ ├── permissions_list_group_item.xml
│ │ ├── permissions_list_unknown_item.xml
│ │ ├── settings_fragment.xml
│ │ └── some_item_line.xml
│ │ ├── menu
│ │ ├── bottom_navigation_menu.xml
│ │ ├── menu_app_details.xml
│ │ ├── menu_main.xml
│ │ └── menu_settings_index.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── navigation
│ │ ├── bottom_navigation.xml
│ │ └── main_navigation.xml
│ │ ├── values-cs
│ │ └── strings.xml
│ │ ├── values-el
│ │ └── strings.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values-pt
│ │ └── strings.xml
│ │ ├── values-ru
│ │ └── strings.xml
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── file_provider_paths.xml
│ │ ├── preferences_acknowledgements.xml
│ │ ├── preferences_advanced.xml
│ │ ├── preferences_general.xml
│ │ ├── preferences_index.xml
│ │ └── preferences_support.xml
│ ├── test
│ └── java
│ │ ├── eu
│ │ └── darken
│ │ │ └── myperm
│ │ │ └── common
│ │ │ ├── flow
│ │ │ └── DynamicStateFlowTest.kt
│ │ │ └── preferences
│ │ │ ├── FlowPreferenceMoshiTest.kt
│ │ │ └── FlowPreferenceTest.kt
│ │ └── testhelper
│ │ ├── BaseTest.kt
│ │ ├── coroutine
│ │ ├── TestDispatcherProvider.kt
│ │ └── TestExtensions.kt
│ │ ├── flow
│ │ └── FlowTest.kt
│ │ ├── json
│ │ └── JsonExtensions.kt
│ │ ├── livedata
│ │ └── InstantExecutorExtension.kt
│ │ └── preferences
│ │ └── MockSharedPreferencesTest.kt
│ └── testShared
│ └── java
│ └── testhelpers
│ ├── BaseTestInstrumentation.kt
│ ├── IsAUnitTest.kt
│ ├── logging
│ └── JUnitLogger.kt
│ └── preferences
│ ├── MockFlowPreference.kt
│ └── MockSharedPreferences.kt
├── build.gradle.kts
├── buildSrc
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ ├── Dependencies.kt
│ ├── ProjectConfig.kt
│ └── Versions.kt
├── fastlane
├── Fastfile
├── metadata
│ └── android
│ │ ├── cs-CZ
│ │ ├── full_description.txt
│ │ └── short_description.txt
│ │ ├── el-GR
│ │ ├── full_description.txt
│ │ └── short_description.txt
│ │ ├── en-US
│ │ ├── changelogs
│ │ │ └── default.txt
│ │ ├── full_description.txt
│ │ ├── images
│ │ │ ├── featureGraphic.png
│ │ │ ├── icon.png
│ │ │ └── phoneScreenshots
│ │ │ │ ├── screenshot1.png
│ │ │ │ ├── screenshot10.png
│ │ │ │ ├── screenshot2.png
│ │ │ │ ├── screenshot3.png
│ │ │ │ ├── screenshot4.png
│ │ │ │ ├── screenshot5.png
│ │ │ │ ├── screenshot6.png
│ │ │ │ ├── screenshot7.png
│ │ │ │ ├── screenshot8.png
│ │ │ │ └── screenshot9.png
│ │ └── short_description.txt
│ │ ├── es-ES
│ │ ├── full_description.txt
│ │ └── short_description.txt
│ │ └── pt-BR
│ │ ├── full_description.txt
│ │ └── short_description.txt
└── remove_unsupported_languages.sh
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release.sh
├── settings.gradle
└── version.properties
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | github:
3 | - d4rken
4 | custom:
5 | - "https://www.buymeacoffee.com/tydarken"
6 |
--------------------------------------------------------------------------------
/.github/assets/app_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/.github/assets/app_banner.png
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - changelog-ignore
5 | categories:
6 | - title: ":rocket: Enhancements"
7 | labels:
8 | - enhancement
9 | - title: ":lady_beetle: Bug fixes"
10 | labels:
11 | - bug
12 | - title: ":shrug: Other changes"
13 | labels:
14 | - "*"
--------------------------------------------------------------------------------
/.github/workflows/gradle-wrapper-validation.yml:
--------------------------------------------------------------------------------
1 | name: "Validate Gradle Wrapper"
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | validation:
13 | name: "Validation"
14 | runs-on: ubuntu-22.04
15 | steps:
16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
17 | - uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 #v4.3.1
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .DS_Store
5 | /build
6 | /captures
7 | .externalNativeBuild
8 | .cxx
9 | /.idea/**/*
10 | !/.idea/codeStyles/
11 | !/.idea/codeStyles/**/*
12 | *.jks
13 | .local/*
14 | /fastlane/report.xml
15 | /fastlane/Appfile
16 | /fastlane/README.md
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | myperm.darken.eu
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 1.7.3-rc0 10703000
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: minima
2 | plugins:
3 | - jekyll-relative-links
4 | relative_links:
5 | enabled: true
6 | collections: true
7 |
8 | title: "Permission Pilot"
9 | description: "An Android permission explorer"
10 | author: "by Matthias Urhahn"
11 |
12 | include:
13 | - PRIVACY_POLICY.md
14 | exclude:
15 | - buildSrc
16 | - gradle/wrapper
17 | - fastlane
18 | - Gemfile
19 | - Gemfile.lock
20 | - crowdin*
21 | - app
22 |
--------------------------------------------------------------------------------
/_layouts/plain.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {%- include head.html -%}
5 |
6 |
7 |
8 |
9 |
10 | {{ content }}
11 |
12 |
13 |
14 | {%- include footer.html -%}
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/proguard-rules-debug.pro:
--------------------------------------------------------------------------------
1 | -dontobfuscate
2 | -dontshrink
3 | -dontoptimize
4 | -dontpreverify
5 | -keep class eu.darken.myperm.** { *; }
--------------------------------------------------------------------------------
/app/proguard/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -dontobfuscate
--------------------------------------------------------------------------------
/app/src/androidTest/java/eu/darken/myperm/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("eu.darken.myperm", appContext.packageName)
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/eu/darken/myperm/HiltTestRunner.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.test.runner.AndroidJUnitRunner
6 | import dagger.hilt.android.testing.HiltTestApplication
7 |
8 | class HiltTestRunner : AndroidJUnitRunner() {
9 |
10 | override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
11 | return super.newApplication(cl, HiltTestApplication::class.java.name, context)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/debug/java/eu/darken/myperm/HiltTestActivity.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import dagger.hilt.android.AndroidEntryPoint
5 |
6 | @AndroidEntryPoint
7 | class HiltTestActivity : AppCompatActivity()
--------------------------------------------------------------------------------
/app/src/foss/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/foss/java/eu/darken/myperm/common/debug/DebugModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import eu.darken.myperm.common.debug.autoreport.AutomaticBugReporter
8 | import eu.darken.myperm.common.debug.autoreporting.FossAutoReporting
9 | import eu.darken.myperm.common.debug.recording.core.ActualRecorderModule
10 | import eu.darken.myperm.common.debug.recording.core.RecorderModule
11 | import javax.inject.Singleton
12 |
13 | @InstallIn(SingletonComponent::class)
14 | @Module
15 | abstract class DebugModule {
16 | @Binds
17 | @Singleton
18 | abstract fun autoreporting(foss: FossAutoReporting): AutomaticBugReporter
19 |
20 | @Binds
21 | @Singleton
22 | abstract fun recorder(mod: ActualRecorderModule): RecorderModule
23 | }
--------------------------------------------------------------------------------
/app/src/foss/java/eu/darken/myperm/common/debug/autoreporting/FossAutoReporting.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.autoreporting
2 |
3 | import android.app.Application
4 | import eu.darken.myperm.common.debug.autoreport.AutomaticBugReporter
5 | import eu.darken.myperm.common.debug.logging.log
6 | import javax.inject.Inject
7 | import javax.inject.Singleton
8 |
9 | @Singleton
10 | class FossAutoReporting @Inject constructor() : AutomaticBugReporter {
11 | override fun setup(application: Application) {
12 | // NOOP
13 | }
14 |
15 | override fun notify(throwable: Throwable) {
16 | throw IllegalStateException("Who initliazed this? Without setup no calls to here!")
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/foss/java/eu/darken/myperm/common/upgrade/UpgradeModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import eu.darken.myperm.common.upgrade.core.UpgradeControlFoss
8 | import javax.inject.Singleton
9 |
10 | @InstallIn(SingletonComponent::class)
11 | @Module
12 | abstract class UpgradeModule {
13 | @Binds
14 | @Singleton
15 | abstract fun control(foss: UpgradeControlFoss): UpgradeRepo
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/foss/java/eu/darken/myperm/common/upgrade/core/FossCache.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core
2 |
3 | import android.content.Context
4 | import com.squareup.moshi.Moshi
5 | import dagger.hilt.android.qualifiers.ApplicationContext
6 | import eu.darken.myperm.common.preferences.createFlowPreference
7 | import javax.inject.Inject
8 | import javax.inject.Singleton
9 |
10 | @Singleton
11 | class FossCache @Inject constructor(
12 | @ApplicationContext context: Context,
13 | moshi: Moshi
14 | ) {
15 |
16 | private val preferences = context.getSharedPreferences("settings_foss", Context.MODE_PRIVATE)
17 |
18 | val upgrade = preferences.createFlowPreference(
19 | key = "foss.upgrade",
20 | moshi = moshi,
21 | defaultValue = null,
22 | )
23 |
24 | }
--------------------------------------------------------------------------------
/app/src/foss/java/eu/darken/myperm/common/upgrade/core/FossUpgrade.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 | import java.time.Instant
6 |
7 | @JsonClass(generateAdapter = true)
8 | data class FossUpgrade(
9 | val upgradedAt: Instant,
10 | val reason: Reason
11 | ) {
12 | @JsonClass(generateAdapter = false)
13 | enum class Reason {
14 | @Json(name = "foss.upgrade.reason.donated") DONATED,
15 | @Json(name = "foss.upgrade.reason.alreadydonated") ALREADY_DONATED,
16 | @Json(name = "foss.upgrade.reason.nomoney") NO_MONEY;
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/foss/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 捐赠
4 | 我已经捐赠过了
5 | 我把所有的钱都花在外卖上了
6 |
--------------------------------------------------------------------------------
/app/src/foss/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Donate
4 | I already donated
5 | I spend all my money on takeout
6 |
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/debug/DebugModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import eu.darken.myperm.common.debug.autoreport.AutomaticBugReporter
8 | import eu.darken.myperm.common.debug.autoreport.GooglePlayReporting
9 | import eu.darken.myperm.common.debug.recording.core.NoopRecorderModule
10 | import eu.darken.myperm.common.debug.recording.core.RecorderModule
11 | import javax.inject.Singleton
12 |
13 | @InstallIn(SingletonComponent::class)
14 | @Module
15 | abstract class DebugModule {
16 | @Binds
17 | @Singleton
18 | abstract fun autoreporting(foss: GooglePlayReporting): AutomaticBugReporter
19 |
20 | @Binds
21 | @Singleton
22 | abstract fun recorder(foss: NoopRecorderModule): RecorderModule
23 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/debug/autoreport/GooglePlayReporting.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.autoreport
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import dagger.hilt.android.qualifiers.ApplicationContext
6 | import eu.darken.myperm.common.InstallId
7 | import eu.darken.myperm.common.debug.logging.logTag
8 | import javax.inject.Inject
9 | import javax.inject.Singleton
10 |
11 | @Singleton
12 | class GooglePlayReporting @Inject constructor(
13 | @ApplicationContext private val context: Context,
14 | private val bugReportSettings: DebugSettings,
15 | private val installId: InstallId,
16 | ) : AutomaticBugReporter {
17 |
18 | override fun setup(application: Application) {
19 | // NOOP
20 | }
21 |
22 | override fun notify(throwable: Throwable) {
23 | // NOOP
24 | }
25 |
26 | companion object {
27 | private val TAG = logTag("Debug", "GooglePlayReporting")
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/debug/recording/core/NoopRecorderModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.recording.core
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.flowOf
5 | import java.io.File
6 | import javax.inject.Inject
7 | import javax.inject.Singleton
8 |
9 | @Singleton
10 | class NoopRecorderModule @Inject constructor() : RecorderModule {
11 | override val state: Flow = flowOf(RecorderModule.State(isAvailable = false))
12 |
13 | override suspend fun startRecorder(): File {
14 | throw NotImplementedError()
15 | }
16 |
17 | override suspend fun stopRecorder(): File? {
18 | throw NotImplementedError()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/UpgradeModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import eu.darken.myperm.common.upgrade.core.UpgradeRepoGplay
8 | import javax.inject.Singleton
9 |
10 | @InstallIn(SingletonComponent::class)
11 | @Module
12 | abstract class UpgradeModule {
13 | @Binds
14 | @Singleton
15 | abstract fun control(gplay: UpgradeRepoGplay): UpgradeRepo
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/BillingCache.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core
2 |
3 | import android.content.Context
4 | import dagger.hilt.android.qualifiers.ApplicationContext
5 | import eu.darken.myperm.common.preferences.createFlowPreference
6 | import javax.inject.Inject
7 | import javax.inject.Singleton
8 |
9 | @Singleton
10 | class BillingCache @Inject constructor(
11 | @ApplicationContext private val context: Context,
12 | ) {
13 |
14 | private val preferences = context.getSharedPreferences("settings_gplay", Context.MODE_PRIVATE)
15 |
16 | val lastProStateAt = preferences.createFlowPreference(
17 | "gplay.cache.lastProAt",
18 | 0L
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/MyPermSku.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core
2 |
3 | import eu.darken.myperm.common.BuildConfigWrap
4 | import eu.darken.myperm.common.upgrade.core.data.AvailableSku
5 | import eu.darken.myperm.common.upgrade.core.data.Sku
6 |
7 | enum class MyPermSku constructor(override val sku: Sku) : AvailableSku {
8 | PRO_UPGRADE(Sku("${BuildConfigWrap.APPLICATION_ID}.iap.upgrade.pro"))
9 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/client/BillingClientExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.client
2 |
3 | import com.android.billingclient.api.BillingClient
4 | import com.android.billingclient.api.BillingResult
5 |
6 | internal val BillingResult.isSuccess: Boolean
7 | get() = responseCode == BillingClient.BillingResponseCode.OK
8 |
9 | internal val BillingResult.isGplayUnavailableTemporary: Boolean
10 | get() = setOf(
11 | BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
12 | BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
13 | BillingClient.BillingResponseCode.SERVICE_TIMEOUT
14 | ).contains(responseCode)
15 |
16 | internal val BillingResult.isGplayUnavailablePermanent: Boolean
17 | get() = responseCode == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/client/BillingException.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.client
2 |
3 | open class BillingException(override val message: String) : Exception()
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/client/BillingResultException.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.client
2 |
3 | import com.android.billingclient.api.BillingResult
4 |
5 | class BillingResultException(val result: BillingResult) : BillingException(result.debugMessage) {
6 |
7 | override fun toString(): String =
8 | "BillingResultException(code=${result.responseCode}, message=${result.debugMessage})"
9 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/client/GplayServiceUnavailableException.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.client
2 |
3 | import android.content.Context
4 | import eu.darken.myperm.R
5 | import eu.darken.myperm.common.error.HasLocalizedError
6 | import eu.darken.myperm.common.error.LocalizedError
7 |
8 | class GplayServiceUnavailableException(cause: Throwable) : Exception("Google Play services are unavailable.", cause),
9 | HasLocalizedError {
10 | override fun getLocalizedError(context: Context): LocalizedError = LocalizedError(
11 | throwable = this,
12 | label = "Google Play Services Unavailable",
13 | description = context.getString(R.string.upgrades_gplay_unavailable_error)
14 | )
15 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/data/AvailableSku.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.data
2 |
3 | interface AvailableSku {
4 | val sku: Sku
5 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/data/BillingData.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.data
2 |
3 | import com.android.billingclient.api.Purchase
4 |
5 | data class BillingData(
6 | val purchases: Collection
7 | ) {
8 | val purchasedSkus: Collection
9 | get() = purchases.map { it.toPurchasedSku() }.flatten()
10 |
11 | private fun Purchase.toPurchasedSku(): Collection = skus.map {
12 | PurchasedSku(Sku(it), this)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/data/PurchasedSku.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.data
2 |
3 | import com.android.billingclient.api.Purchase
4 |
5 | data class PurchasedSku(val sku: Sku, val purchase: Purchase) {
6 | override fun toString(): String = "IAP(sku=$sku, purchase=${purchase.skus})"
7 | }
--------------------------------------------------------------------------------
/app/src/gplay/java/eu/darken/myperm/common/upgrade/core/data/Sku.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade.core.data
2 |
3 | import com.android.billingclient.api.SkuDetails
4 |
5 | data class Sku(
6 | val id: String
7 | ) {
8 | data class Details(
9 | val sku: Sku,
10 | val details: Collection,
11 | )
12 | }
--------------------------------------------------------------------------------
/app/src/gplay/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Google Play services are unavailable.
4 | No purchases found. Are you using the right account?
5 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/AppRepoExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core
2 |
3 | import eu.darken.myperm.apps.core.container.BasePkg
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.filterIsInstance
6 | import kotlinx.coroutines.flow.map
7 |
8 |
9 | val AppRepo.apps: Flow>
10 | get() = state.filterIsInstance().map { it.pkgs }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/PackageInfoExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core
2 |
3 | import android.content.pm.ApplicationInfo
4 |
5 | val ApplicationInfo.isSystemApp: Boolean
6 | get() = this.flags and ApplicationInfo.FLAG_SYSTEM != 0
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/container/BasePkg.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.container
2 |
3 | import eu.darken.myperm.apps.core.Pkg
4 | import eu.darken.myperm.apps.core.features.Installed
5 | import eu.darken.myperm.apps.core.features.ReadableApk
6 |
7 | sealed class BasePkg : Pkg, ReadableApk, Installed
8 |
9 | fun BasePkg.isOrHasProfiles() = twins.isNotEmpty() || this is SecondaryProfilePkg
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/AccessibilityService.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | import android.accessibilityservice.AccessibilityServiceInfo
4 | import android.content.pm.PackageInfo
5 | import android.content.pm.PackageManager
6 | import eu.darken.myperm.common.IPCFunnel
7 | import eu.darken.myperm.permissions.core.known.APerm
8 |
9 | data class AccessibilityService(
10 | val isEnabled: Boolean,
11 | val label: String,
12 | )
13 |
14 | suspend fun PackageInfo.determineAccessibilityServices(ipcFunnel: IPCFunnel): List {
15 | val pkgInfo = ipcFunnel.packageManager.getPackageInfo(packageName, PackageManager.GET_SERVICES)
16 |
17 | val enabledAcs =
18 | ipcFunnel.accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
19 |
20 | return pkgInfo?.services
21 | ?.filter { it.permission == APerm.BIND_ACCESSIBILITY_SERVICE.id.value }
22 | ?.map {
23 | AccessibilityService(
24 | isEnabled = enabledAcs.any { acs -> acs.resolveInfo.serviceInfo.name == it.name },
25 | label = it.name ?: it.packageName
26 | )
27 | }
28 | ?: emptyList()
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/AppStore.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | import eu.darken.myperm.apps.core.Pkg
4 |
5 | interface AppStore : Pkg {
6 |
7 | val urlGenerator: ((Pkg.Id) -> String)?
8 | get() = null
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/BatteryOptimization.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | import android.content.pm.PackageInfo
4 | import eu.darken.myperm.common.IPCFunnel
5 | import eu.darken.myperm.common.hasApiLevel
6 | import eu.darken.myperm.permissions.core.known.APerm
7 |
8 | enum class BatteryOptimization {
9 | IGNORED,
10 | OPTIMIZED,
11 | MANAGED_BY_SYSTEM,
12 | UNKNOWN,
13 | }
14 |
15 | suspend fun PackageInfo.determineBatteryOptimization(ipcFunnel: IPCFunnel): BatteryOptimization {
16 | if (!hasApiLevel(23)) return BatteryOptimization.IGNORED
17 | if (requestedPermissions == null) return BatteryOptimization.MANAGED_BY_SYSTEM
18 | if (requestedPermissions.none { it == APerm.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS.id.value }) {
19 | return BatteryOptimization.MANAGED_BY_SYSTEM
20 | }
21 |
22 | return if (ipcFunnel.powerManager.isIgnoringBatteryOptimizations(packageName)) {
23 | BatteryOptimization.IGNORED
24 | } else {
25 | BatteryOptimization.OPTIMIZED
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/ExtraPermissionScanner.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | import android.content.pm.PackageInfo
4 | import android.content.pm.PackageManager
5 | import eu.darken.myperm.common.IPCFunnel
6 | import eu.darken.myperm.permissions.core.known.AExtraPerm
7 |
8 | suspend fun PackageInfo.determineSpecialPermissions(ipcFunnel: IPCFunnel): Collection {
9 | val permissions = mutableSetOf()
10 |
11 | val withActivities = try {
12 | ipcFunnel.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
13 | } catch (e: PackageManager.NameNotFoundException) {
14 | null
15 | }
16 |
17 | if (withActivities?.activities?.any { it.flags and 0x400000 != 0 } == true) {
18 | permissions.add(UsesPermission.Unknown(AExtraPerm.PICTURE_IN_PICTURE.id))
19 | }
20 |
21 | return permissions
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/Installed.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | import android.content.pm.PackageInfo
4 | import android.os.UserHandle
5 | import eu.darken.myperm.apps.core.Pkg
6 | import java.time.Instant
7 |
8 | interface Installed : Pkg {
9 | val packageInfo: PackageInfo
10 | val userHandle: UserHandle
11 |
12 | // Weird overflow when using default interface impl here?
13 | // https://github.com/d4rken-org/permission-pilot/issues/173
14 | val isSystemApp: Boolean
15 |
16 | val installedAt: Instant?
17 | get() = packageInfo.firstInstallTime.takeIf { it != 0L }?.let { Instant.ofEpochMilli(it) }
18 |
19 | val updatedAt: Instant?
20 | get() = packageInfo.lastUpdateTime.takeIf { it != 0L }?.let { Instant.ofEpochMilli(it) }
21 |
22 | // Same user id
23 | val siblings: Collection
24 |
25 | // Extra user profile
26 | val twins: Collection
27 |
28 | val installerInfo: InstallerInfo
29 |
30 | val internetAccess: InternetAccess
31 |
32 | val batteryOptimization: BatteryOptimization
33 | get() = BatteryOptimization.MANAGED_BY_SYSTEM
34 |
35 | val accessibilityServices: Collection
36 | get() = emptyList()
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/InternetAccess.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | enum class InternetAccess {
4 | DIRECT,
5 | INDIRECT,
6 | NONE,
7 | UNKNOWN
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/PermissionState.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/SecondaryPkg.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | interface SecondaryPkg
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/core/features/UsesPermission.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.core.features
2 |
3 | import android.content.pm.PackageInfo
4 | import eu.darken.myperm.apps.core.Pkg
5 | import eu.darken.myperm.permissions.core.Permission
6 |
7 | sealed class UsesPermission {
8 | abstract val id: Permission.Id
9 | abstract val status: Status
10 |
11 | enum class Status {
12 | GRANTED,
13 | GRANTED_IN_USE,
14 | DENIED,
15 | UNKNOWN,
16 | }
17 |
18 | data class WithState(override val id: Permission.Id, val flags: Int?) : UsesPermission() {
19 |
20 | override val status: Status = when {
21 | flags == null -> Status.UNKNOWN
22 | flags and PackageInfo.REQUESTED_PERMISSION_GRANTED != 0 -> Status.GRANTED
23 | else -> Status.DENIED
24 | }
25 | }
26 |
27 | data class Unknown(override val id: Permission.Id) : UsesPermission() {
28 | override val status: Status = Status.UNKNOWN
29 | }
30 | }
31 |
32 | val UsesPermission.isGranted: Boolean
33 | get() = status == UsesPermission.Status.GRANTED
34 |
35 | fun Pkg.getPermissionUses(id: Permission.Id): UsesPermission =
36 | (this as? ReadableApk)?.requestedPermissions
37 | ?.filterIsInstance()
38 | ?.singleOrNull { it.id == id }
39 | ?: UsesPermission.Unknown(id)
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/ui/details/AppDetailsEvents.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.ui.details
2 |
3 | import eu.darken.myperm.apps.core.Pkg
4 | import eu.darken.myperm.permissions.core.features.PermissionAction
5 |
6 | sealed class AppDetailsEvents {
7 | data class ShowAppSystemDetails(val pkg: Pkg) : AppDetailsEvents()
8 | data class PermissionEvent(val permAction: PermissionAction) : AppDetailsEvents()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/ui/list/AppsEvents.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.ui.list
2 |
3 | import eu.darken.myperm.apps.core.Pkg
4 | import eu.darken.myperm.permissions.core.Permission
5 | import eu.darken.myperm.permissions.core.features.PermissionAction
6 |
7 | sealed class AppsEvents {
8 | data class ShowFilterDialog(val options: AppsFilterOptions) : AppsEvents()
9 |
10 | data class ShowSortDialog(val options: AppsSortOptions) : AppsEvents()
11 |
12 | data class ShowPermissionSnackbar(val permission: Permission) : AppsEvents()
13 |
14 | data class ShowAppSystemDetails(val pkg: Pkg) : AppsEvents()
15 |
16 | data class PermissionEvent(val permAction: PermissionAction) : AppsEvents()
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/apps/ui/list/SortDialog.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.apps.ui.list
2 |
3 | import android.app.Activity
4 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
5 | import eu.darken.myperm.R
6 | import eu.darken.myperm.common.dialog.BaseDialogBuilder
7 |
8 | class SortDialog(private val activity: Activity) : BaseDialogBuilder(activity) {
9 |
10 | fun show(
11 | options: AppsSortOptions,
12 | onResult: (AppsSortOptions) -> Unit
13 | ) {
14 | val itemLabels = AppsSortOptions.Sort.values().map {
15 | getString(it.labelRes)
16 | }.toTypedArray()
17 |
18 | var checkedItem = AppsSortOptions.Sort.values().indexOf(options.mainSort)
19 |
20 | MaterialAlertDialogBuilder(context).apply {
21 | setTitle(R.string.general_sort_action)
22 |
23 | setSingleChoiceItems(itemLabels, checkedItem) { dialog, which ->
24 | val new = options.copy(
25 | mainSort = AppsSortOptions.Sort.values()[which]
26 | )
27 | onResult(new)
28 | dialog.dismiss()
29 | }
30 | }.show()
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/AndroidVersionCodes.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | enum class AndroidVersionCodes(
4 | val label: String,
5 | val versionName: String,
6 | val apiLevel: Int,
7 | ) {
8 |
9 | Android13("Android 13", "13", 33),
10 | Android12L("Android 12L", "12", 32),
11 | Android12("Android 12", "12", 31),
12 | Android11("Android 11", "11", 30),
13 | Android10("Android 10", "10", 29),
14 | Pie("Pie", "9", 28),
15 | Oreo2("Oreo", "8.1.0", 27),
16 | Oreo1("Oreo", "8.0.0", 26),
17 | Nougat2("Nougat", "7.1", 25),
18 | Nougat1("Nougat", "7.0", 24),
19 | Marshmallow("Marshmallow", "6.0", 23),
20 | Lollipop2("Lollipop", "5.1", 22),
21 | Lollipop1("Lollipop", "5.0", 21),
22 | KitKat("KitKat", "4.4.x", 19),
23 | Jelly3("Jelly Bean", "4.3.x", 18),
24 | Jelly2("Jelly Bean", "4.2.x", 17),
25 | Jelly1("Jelly Bean", "4.1.x", 16),
26 | Ice2("Ice Cream Sandwich", "4.0.3 - 4.0.4", 15),
27 | Ice1("Ice Cream Sandwich", "4.0.1 - 4.0.2", 14),
28 | Honeycomb3("Honeycomb", "3.2.x", 13),
29 | Honeycomb2("Honeycomb", "3.1", 12),
30 | Honeycomb1("Honeycomb", "3.0", 11),
31 | Gingerbread2("Gingerbread", "2.3.3 - 2.3.7", 10),
32 | Gingerbread1("Gingerbread", "2.3 - 2.3.2", 9),
33 |
34 | ;
35 |
36 | val longFormat: String = "$label (${versionName}) [API $apiLevel]"
37 |
38 | companion object {
39 | val current = values().singleOrNull { it.apiLevel == BuildWrap.VersionWrap.SDK_INT }
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/BuildConfigWrap.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import androidx.annotation.Keep
4 | import eu.darken.myperm.BuildConfig
5 |
6 |
7 | // Can't be const because that prevents them from being mocked in tests
8 | @Suppress("MayBeConstant")
9 | @Keep
10 | object BuildConfigWrap {
11 | val APPLICATION_ID = BuildConfig.APPLICATION_ID
12 | val DEBUG: Boolean = BuildConfig.DEBUG
13 | val BUILD_TYPE: BuildType = when (val typ = BuildConfig.BUILD_TYPE) {
14 | "debug" -> BuildType.DEV
15 | "beta" -> BuildType.BETA
16 | "release" -> BuildType.RELEASE
17 | else -> throw IllegalArgumentException("Unknown buildtype: $typ")
18 | }
19 |
20 | enum class BuildType {
21 | DEV,
22 | BETA,
23 | RELEASE,
24 | ;
25 | }
26 |
27 | val FLAVOR: Flavor = when (val flav = BuildConfig.FLAVOR) {
28 | "gplay" -> Flavor.GPLAY
29 | "foss" -> Flavor.FOSS
30 | else -> throw IllegalStateException("Unknown flavor: $flav")
31 | }
32 |
33 | enum class Flavor {
34 | GPLAY,
35 | FOSS,
36 | ;
37 | }
38 |
39 | val VERSION_CODE: Long = BuildConfig.VERSION_CODE.toLong()
40 | val VERSION_NAME: String = BuildConfig.VERSION_NAME
41 | val GIT_SHA: String = BuildConfig.GITSHA
42 |
43 | val VERSION_DESCRIPTION: String = "v$VERSION_NAME ($VERSION_CODE) ~ $GIT_SHA/$FLAVOR/$BUILD_TYPE"
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/BuildWrap.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import android.os.Build
4 |
5 | // Can't be const because that prevents them from being mocked in tests
6 | @Suppress("MayBeConstant")
7 | object BuildWrap {
8 |
9 | val VERSION = VersionWrap
10 |
11 | object VersionWrap {
12 | val SDK_INT = Build.VERSION.SDK_INT
13 | }
14 | }
15 |
16 | fun hasApiLevel(level: Int): Boolean = BuildWrap.VERSION.SDK_INT >= level
17 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/InstallId.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import android.content.Context
4 | import dagger.hilt.android.qualifiers.ApplicationContext
5 | import eu.darken.myperm.common.debug.logging.log
6 | import eu.darken.myperm.common.debug.logging.logTag
7 | import java.io.File
8 | import java.util.*
9 | import java.util.regex.Pattern
10 | import javax.inject.Inject
11 | import javax.inject.Singleton
12 |
13 | @Singleton
14 | class InstallId @Inject constructor(
15 | @ApplicationContext private val context: Context,
16 | ) {
17 | private val installIDFile = File(context.filesDir, INSTALL_ID_FILENAME)
18 | val id: String by lazy {
19 | val existing = if (installIDFile.exists()) {
20 | installIDFile.readText().also {
21 | if (!UUID_PATTERN.matcher(it).matches()) throw IllegalStateException("Invalid InstallID: $it")
22 | }
23 | } else {
24 | null
25 | }
26 |
27 | return@lazy existing ?: UUID.randomUUID().toString().also {
28 | log(TAG) { "New install ID created: $it" }
29 | installIDFile.writeText(it)
30 | }
31 | }
32 |
33 | companion object {
34 | private val TAG: String = logTag("InstallID")
35 | private val UUID_PATTERN = Pattern.compile(
36 | "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
37 | )
38 |
39 | private const val INSTALL_ID_FILENAME = "installid"
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/LiveDataExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import androidx.fragment.app.Fragment
5 | import androidx.lifecycle.LiveData
6 | import androidx.viewbinding.ViewBinding
7 |
8 |
9 | fun LiveData.observe2(fragment: Fragment, callback: (T) -> Unit) {
10 | observe(fragment.viewLifecycleOwner) { callback.invoke(it) }
11 | }
12 |
13 | inline fun LiveData.observe2(
14 | fragment: Fragment,
15 | ui: VB,
16 | crossinline callback: VB.(T) -> Unit
17 | ) {
18 | observe(fragment.viewLifecycleOwner) { callback.invoke(ui, it) }
19 | }
20 |
21 | fun LiveData.observe2(activity: AppCompatActivity, callback: (T) -> Unit) {
22 | observe(activity) { callback.invoke(it) }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/PrivacyPolicy.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | object PrivacyPolicy {
4 | const val URL = "https://myperm.darken.eu/privacy"
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import android.content.Context
4 | import android.text.SpannableString
5 | import android.text.style.ForegroundColorSpan
6 | import androidx.annotation.ColorRes
7 | import androidx.core.content.ContextCompat
8 | import java.util.*
9 |
10 | fun String.colorString(context: Context, @ColorRes colorRes: Int): SpannableString {
11 | val colored = SpannableString(this)
12 | colored.setSpan(ForegroundColorSpan(ContextCompat.getColor(context, colorRes)), 0, this.length, 0)
13 | return colored
14 | }
15 |
16 | fun String.capitalizeFirstLetter(): String = replaceFirstChar {
17 | if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/UnitConverter.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import android.content.Context
4 | import android.util.TypedValue
5 |
6 | object UnitConverter {
7 | fun dpToPx(context: Context, dp: Float): Int {
8 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.resources.displayMetrics).toInt()
9 | }
10 |
11 | fun spToPx(context: Context, sp: Float): Float {
12 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.resources.displayMetrics)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/WebpageTool.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import dagger.Reusable
7 | import dagger.hilt.android.qualifiers.ApplicationContext
8 | import eu.darken.myperm.common.debug.logging.Logging.Priority.ERROR
9 | import eu.darken.myperm.common.debug.logging.log
10 | import javax.inject.Inject
11 |
12 | @Reusable
13 | class WebpageTool @Inject constructor(
14 | @ApplicationContext private val context: Context,
15 | ) {
16 |
17 | fun open(address: String) {
18 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(address)).apply {
19 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
20 | }
21 | try {
22 | context.startActivity(intent)
23 | } catch (e: Exception) {
24 | log(ERROR) { "Failed to launch" }
25 | }
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/coil/PermissionIconFetcher.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.coil
2 |
3 | import android.graphics.Color
4 | import android.graphics.drawable.ColorDrawable
5 | import coil.ImageLoader
6 | import coil.decode.DataSource
7 | import coil.fetch.DrawableResult
8 | import coil.fetch.FetchResult
9 | import coil.fetch.Fetcher
10 | import coil.request.Options
11 | import eu.darken.myperm.common.IPCFunnel
12 | import eu.darken.myperm.permissions.core.Permission
13 | import javax.inject.Inject
14 |
15 |
16 | class PermissionIconFetcher @Inject constructor(
17 | private val ipcFunnel: IPCFunnel,
18 | private val data: Permission,
19 | private val options: Options,
20 | ) : Fetcher {
21 | override suspend fun fetch(): FetchResult = DrawableResult(
22 | drawable = ipcFunnel.execute { data.getIcon(options.context) } ?: ColorDrawable(Color.TRANSPARENT),
23 | isSampled = false,
24 | dataSource = DataSource.MEMORY
25 | )
26 |
27 | class Factory @Inject constructor(
28 | private val ipcFunnel: IPCFunnel,
29 | ) : Fetcher.Factory {
30 |
31 | override fun create(
32 | data: Permission,
33 | options: Options,
34 | imageLoader: ImageLoader
35 | ): Fetcher = PermissionIconFetcher(ipcFunnel, data, options)
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/collections/MapExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.collections
2 |
3 | inline fun Map.mutate(block: MutableMap.() -> Unit): Map {
4 | return toMutableMap().apply(block).toMap()
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/compression/Zipper.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.compression
2 |
3 | import eu.darken.myperm.common.debug.logging.Logging.Priority.VERBOSE
4 | import eu.darken.myperm.common.debug.logging.log
5 | import eu.darken.myperm.common.debug.logging.logTag
6 | import java.io.BufferedInputStream
7 | import java.io.BufferedOutputStream
8 | import java.io.FileInputStream
9 | import java.io.FileOutputStream
10 | import java.util.zip.ZipEntry
11 | import java.util.zip.ZipOutputStream
12 |
13 | // https://stackoverflow.com/a/48598099/1251958
14 | class Zipper {
15 |
16 | @Throws(Exception::class)
17 | fun zip(files: Array, zipFile: String) {
18 |
19 | var origin: BufferedInputStream?
20 | val out = ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile)))
21 |
22 | for (i in files.indices) {
23 | log(TAG, VERBOSE) { "Compressing ${files[i]} into $zipFile" }
24 | origin = BufferedInputStream(FileInputStream(files[i]), BUFFER)
25 |
26 | val entry = ZipEntry(files[i].substring(files[i].lastIndexOf("/") + 1))
27 | out.putNextEntry(entry)
28 |
29 | origin.use { input -> input.copyTo(out) }
30 | }
31 |
32 | out.finish()
33 | out.close()
34 | }
35 |
36 | companion object {
37 | internal val TAG = logTag("Zipper")
38 | const val BUFFER = 2048
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/coroutine/AppCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.coroutine
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.SupervisorJob
6 | import javax.inject.Inject
7 | import javax.inject.Qualifier
8 | import javax.inject.Singleton
9 | import kotlin.coroutines.CoroutineContext
10 |
11 | @Singleton
12 | class AppCoroutineScope @Inject constructor() : CoroutineScope {
13 | override val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Default
14 | }
15 |
16 | @Qualifier
17 | @MustBeDocumented
18 | @Retention(AnnotationRetention.RUNTIME)
19 | annotation class AppScope
20 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/coroutine/CoroutineModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.coroutine
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import kotlinx.coroutines.CoroutineScope
8 |
9 | @InstallIn(SingletonComponent::class)
10 | @Module
11 | abstract class CoroutineModule {
12 |
13 | @Binds
14 | abstract fun dispatcherProvider(defaultProvider: DefaultDispatcherProvider): DispatcherProvider
15 |
16 | @Binds
17 | @AppScope
18 | abstract fun appscope(appCoroutineScope: AppCoroutineScope): CoroutineScope
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/coroutine/DefaultDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.coroutine
2 |
3 | import javax.inject.Inject
4 | import javax.inject.Singleton
5 |
6 | @Singleton
7 | class DefaultDispatcherProvider @Inject constructor() : DispatcherProvider
8 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/coroutine/DispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.coroutine
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | // Need this to improve testing
7 | // Can currently only replace the main-thread dispatcher.
8 | // https://github.com/Kotlin/kotlinx.coroutines/issues/1365
9 | @Suppress("PropertyName", "VariableNaming")
10 | interface DispatcherProvider {
11 | val Default: CoroutineDispatcher
12 | get() = Dispatchers.Default
13 | val Main: CoroutineDispatcher
14 | get() = Dispatchers.Main
15 | val MainImmediate: CoroutineDispatcher
16 | get() = Dispatchers.Main.immediate
17 | val Unconfined: CoroutineDispatcher
18 | get() = Dispatchers.Unconfined
19 | val IO: CoroutineDispatcher
20 | get() = Dispatchers.IO
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/dagger/AndroidModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.dagger
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.core.app.NotificationManagerCompat
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import javax.inject.Singleton
11 |
12 | @InstallIn(SingletonComponent::class)
13 | @Module
14 | class AndroidModule {
15 |
16 | @Provides
17 | @Singleton
18 | fun context(app: Application): Context = app.applicationContext
19 |
20 | @Provides
21 | @Singleton
22 | fun notificationManager(context: Context): NotificationManagerCompat =
23 | NotificationManagerCompat.from(context)
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/debug/Bugs.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug
2 |
3 | import eu.darken.myperm.common.debug.autoreport.AutomaticBugReporter
4 | import eu.darken.myperm.common.debug.logging.Logging.Priority.VERBOSE
5 | import eu.darken.myperm.common.debug.logging.Logging.Priority.WARN
6 | import eu.darken.myperm.common.debug.logging.log
7 | import eu.darken.myperm.common.debug.logging.logTag
8 |
9 | object Bugs {
10 | var reporter: AutomaticBugReporter? = null
11 | fun report(exception: Exception) {
12 | log(TAG, VERBOSE) { "Reporting $exception" }
13 | if (reporter == null) {
14 | log(TAG, WARN) { "Bug tracking not initialized yet." }
15 | return
16 | }
17 | reporter?.notify(exception)
18 | }
19 |
20 | private val TAG = logTag("Debug", "Bugs")
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/debug/autoreport/AutomaticBugReporter.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.autoreport
2 |
3 | import android.app.Application
4 |
5 | interface AutomaticBugReporter {
6 |
7 | fun setup(application: Application)
8 |
9 | fun notify(throwable: Throwable)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/debug/autoreport/DebugSettings.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.autoreport
2 |
3 | import android.content.Context
4 | import dagger.hilt.android.qualifiers.ApplicationContext
5 | import eu.darken.myperm.common.BuildConfigWrap
6 | import eu.darken.myperm.common.preferences.createFlowPreference
7 | import javax.inject.Inject
8 | import javax.inject.Singleton
9 |
10 | @Singleton
11 | class DebugSettings @Inject constructor(
12 | @ApplicationContext private val context: Context,
13 | ) {
14 |
15 | private val prefs by lazy {
16 | context.getSharedPreferences("debug_settings", Context.MODE_PRIVATE)
17 | }
18 |
19 | val isAutoReportingEnabled = prefs.createFlowPreference(
20 | key = "debug.bugreport.automatic.enabled",
21 | // Reporting is opt-out for gplay, and opt-in for github builds
22 | defaultValue = BuildConfigWrap.FLAVOR == BuildConfigWrap.Flavor.GPLAY
23 | )
24 |
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/debug/logging/LogExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.logging
2 |
3 | fun logTag(vararg tags: String): String {
4 | val sb = StringBuilder("PP:")
5 | for (i in tags.indices) {
6 | sb.append(tags[i])
7 | if (i < tags.size - 1) sb.append(":")
8 | }
9 | return sb.toString()
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/debug/recording/core/RecorderModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.debug.recording.core
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import java.io.File
5 |
6 | interface RecorderModule {
7 |
8 | val state: Flow
9 |
10 | suspend fun startRecorder(): File
11 |
12 | suspend fun stopRecorder(): File?
13 |
14 | data class State(
15 | val isAvailable: Boolean,
16 | val shouldRecord: Boolean = false,
17 | internal val recorder: Recorder? = null,
18 | val lastLogPath: File? = null,
19 | ) {
20 | val isRecording: Boolean
21 | get() = recorder != null
22 |
23 | val currentLogPath: File?
24 | get() = recorder?.path
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/dialog/BaseDialogBuilder.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.dialog
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import androidx.annotation.StringRes
6 |
7 | abstract class BaseDialogBuilder(private val activity: Activity) {
8 |
9 | fun getString(@StringRes stringRes: Int) = activity.getString(stringRes)
10 |
11 | val context: Context
12 | get() = activity
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/error/ErrorDialog.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.error
2 |
3 | import android.content.Context
4 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
5 |
6 | fun Throwable.asErrorDialogBuilder(
7 | context: Context
8 | ) = MaterialAlertDialogBuilder(context).apply {
9 | val error = this@asErrorDialogBuilder
10 | val localizedError = error.localized(context)
11 |
12 | setTitle(localizedError.label)
13 | setMessage(localizedError.description)
14 |
15 | setPositiveButton(android.R.string.ok) { _, _ ->
16 |
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/error/ErrorEventSource.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.error
2 |
3 | import eu.darken.myperm.common.livedata.SingleLiveEvent
4 |
5 | interface ErrorEventSource {
6 | val errorEvents: SingleLiveEvent
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/error/LocalizedError.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.error
2 |
3 | import android.content.Context
4 | import eu.darken.myperm.R
5 |
6 | interface HasLocalizedError {
7 | fun getLocalizedError(context: Context): LocalizedError
8 | }
9 |
10 | data class LocalizedError(
11 | val throwable: Throwable,
12 | val label: String,
13 | val description: String
14 | ) {
15 | fun asText() = "$label:\n$description"
16 | }
17 |
18 | fun Throwable.localized(c: Context): LocalizedError = when {
19 | this is HasLocalizedError -> this.getLocalizedError(c)
20 | localizedMessage != null -> LocalizedError(
21 | throwable = this,
22 | label = "${c.getString(R.string.general_error_label)}: ${this::class.simpleName!!}",
23 | description = localizedMessage ?: getStackTracePeek()
24 | )
25 | else -> LocalizedError(
26 | throwable = this,
27 | label = "${c.getString(R.string.general_error_label)}: ${this::class.simpleName!!}",
28 | description = getStackTracePeek()
29 | )
30 | }
31 |
32 | private fun Throwable.getStackTracePeek() = this.stackTraceToString()
33 | .lines()
34 | .filterIndexed { index, _ -> index > 1 }
35 | .take(3)
36 | .joinToString("\n")
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/error/ThrowableExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.error
2 |
3 | import java.io.PrintWriter
4 | import java.io.StringWriter
5 | import java.lang.reflect.InvocationTargetException
6 | import kotlin.reflect.KClass
7 |
8 | val Throwable.causes: Sequence
9 | get() = sequence {
10 | var subCause = cause
11 | while (subCause != null) {
12 | yield(subCause)
13 | subCause = subCause.cause
14 | }
15 | }
16 |
17 | fun Throwable.getRootCause(): Throwable {
18 | var error = this
19 | while (error.cause != null) {
20 | error = error.cause!!
21 | }
22 | if (error is InvocationTargetException) {
23 | error = error.targetException
24 | }
25 | return error
26 | }
27 |
28 | fun Throwable.hasCause(exceptionClazz: KClass): Boolean {
29 | if (exceptionClazz.isInstance(this)) return true
30 | return exceptionClazz.isInstance(this.getRootCause())
31 | }
32 |
33 | fun Throwable.getStackTraceString(): String {
34 | val sw = StringWriter(256)
35 | val pw = PrintWriter(sw, false)
36 | printStackTrace(pw)
37 | pw.flush()
38 | return sw.toString()
39 | }
40 |
41 | fun Throwable.tryUnwrap(kClass: KClass = RuntimeException::class): Throwable =
42 | if (!kClass.isInstance(this)) this else cause ?: this
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/flow/DynamicStateFlowExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.flow
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/BindableVH.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists
2 |
3 | import androidx.viewbinding.ViewBinding
4 |
5 | interface BindableVH {
6 |
7 | val viewBinding: Lazy
8 |
9 | val onBindData: ViewBindingT.(item: ItemT, payloads: List) -> Unit
10 |
11 | fun bind(item: ItemT, payloads: MutableList = mutableListOf()) = with(viewBinding.value) {
12 | onBindData(item, payloads)
13 | }
14 | }
15 |
16 | @Suppress("unused")
17 | inline fun BindableVH.binding(
18 | payload: Boolean = true,
19 | crossinline block: ViewBindingT.(ItemT) -> Unit,
20 | ): ViewBindingT.(item: ItemT, payloads: List) -> Unit = { item: ItemT, payloads: List ->
21 | val newestItem = when (payload) {
22 | true -> payloads.filterIsInstance().lastOrNull() ?: item
23 | false -> item
24 | }
25 | block(newestItem)
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/DataAdapter.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists
2 |
3 | import androidx.recyclerview.widget.RecyclerView
4 |
5 | interface DataAdapter {
6 | val data: MutableList
7 | }
8 |
9 | fun X.update(newData: List?, notify: Boolean = true) where X : DataAdapter, X : RecyclerView.Adapter<*> {
10 | data.clear()
11 | if (newData != null) data.addAll(newData)
12 | if (notify) notifyDataSetChanged()
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/ListItem.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists
2 |
3 | interface ListItem
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/RecyclerViewExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists
2 |
3 | import androidx.recyclerview.widget.DefaultItemAnimator
4 | import androidx.recyclerview.widget.LinearLayoutManager
5 | import androidx.recyclerview.widget.RecyclerView
6 | import eu.darken.myperm.common.DividerItemDecorator2
7 |
8 | fun RecyclerView.setupDefaults(adapter: RecyclerView.Adapter<*>? = null, dividers: Boolean = true) = apply {
9 | layoutManager = LinearLayoutManager(context)
10 | itemAnimator = DefaultItemAnimator()
11 | if (dividers) addItemDecoration(DividerItemDecorator2(context))
12 | if (adapter != null) this.adapter = adapter
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/differ/AsyncDifferExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.differ
2 |
3 | import androidx.recyclerview.widget.RecyclerView
4 | import eu.darken.myperm.common.lists.modular.ModularAdapter
5 |
6 |
7 | fun X.update(newData: List?)
8 | where X : HasAsyncDiffer, X : RecyclerView.Adapter<*> {
9 |
10 | asyncDiffer.submitUpdate(newData ?: emptyList())
11 | }
12 |
13 | fun A.setupDiffer(): AsyncDiffer
14 | where A : HasAsyncDiffer, A : ModularAdapter<*> =
15 | AsyncDiffer(this)
16 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/differ/DifferItem.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.differ
2 |
3 | import eu.darken.myperm.common.lists.ListItem
4 |
5 | interface DifferItem : ListItem {
6 | val stableId: Long
7 |
8 | val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)?
9 | get() = { old, new -> new }
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/differ/HasAsyncDiffer.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.differ
2 |
3 | interface HasAsyncDiffer {
4 |
5 | val data: List
6 | get() = asyncDiffer.currentList
7 |
8 | val asyncDiffer: AsyncDiffer<*, T>
9 |
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/modular/mods/ClickMod.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.modular.mods
2 |
3 | import eu.darken.myperm.common.lists.modular.ModularAdapter
4 |
5 | class ClickMod constructor(
6 | private val listener: (VHT, Int) -> Unit
7 | ) : ModularAdapter.Module.Binder {
8 |
9 | override fun onBindModularVH(adapter: ModularAdapter, vh: VHT, pos: Int, payloads: MutableList) {
10 | vh.itemView.setOnClickListener { listener.invoke(vh, pos) }
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/modular/mods/DataBinderMod.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.modular.mods
2 |
3 | import androidx.viewbinding.ViewBinding
4 | import eu.darken.myperm.common.lists.BindableVH
5 | import eu.darken.myperm.common.lists.modular.ModularAdapter
6 |
7 | class DataBinderMod constructor(
8 | private val data: List,
9 | private val customBinder: (
10 | (adapter: ModularAdapter, vh: HolderT, pos: Int, payload: MutableList) -> Unit
11 | )? = null
12 | ) : ModularAdapter.Module.Binder where HolderT : BindableVH, HolderT : ModularAdapter.VH {
13 |
14 | override fun onBindModularVH(adapter: ModularAdapter, vh: HolderT, pos: Int, payloads: MutableList) {
15 | customBinder?.invoke(adapter, vh, pos, mutableListOf()) ?: vh.bind(data[pos], payloads)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/modular/mods/SimpleVHCreatorMod.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.modular.mods
2 |
3 | import android.view.ViewGroup
4 | import eu.darken.myperm.common.lists.modular.ModularAdapter
5 |
6 | class SimpleVHCreatorMod constructor(
7 | private val viewType: Int = 0,
8 | private val factory: (ViewGroup) -> HolderT
9 | ) : ModularAdapter.Module.Creator where HolderT : ModularAdapter.VH {
10 |
11 | override fun onCreateModularVH(adapter: ModularAdapter, parent: ViewGroup, viewType: Int): HolderT? {
12 | if (this.viewType != viewType) return null
13 | return factory.invoke(parent)
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/modular/mods/StableIdMod.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.modular.mods
2 |
3 | import androidx.recyclerview.widget.RecyclerView
4 | import eu.darken.myperm.common.lists.differ.DifferItem
5 | import eu.darken.myperm.common.lists.modular.ModularAdapter
6 |
7 | class StableIdMod constructor(
8 | private val data: List,
9 | private val customResolver: (position: Int) -> Long = {
10 | (data[it] as? DifferItem)?.stableId ?: RecyclerView.NO_ID
11 | }
12 | ) : ModularAdapter.Module.ItemId, ModularAdapter.Module.Setup {
13 |
14 | override fun onAdapterReady(adapter: ModularAdapter<*>) {
15 | adapter.setHasStableIds(true)
16 | }
17 |
18 | override fun getItemId(adapter: ModularAdapter<*>, position: Int): Long? {
19 | return customResolver.invoke(position)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/lists/modular/mods/TypedVHCreatorMod.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.lists.modular.mods
2 |
3 | import android.view.ViewGroup
4 | import eu.darken.myperm.common.lists.modular.ModularAdapter
5 |
6 | class TypedVHCreatorMod constructor(
7 | private val typeResolver: (Int) -> Boolean,
8 | private val factory: (ViewGroup) -> HolderT
9 | ) : ModularAdapter.Module.Typing,
10 | ModularAdapter.Module.Creator where HolderT : ModularAdapter.VH {
11 |
12 | private fun ModularAdapter<*>.determineOurViewType(): Int {
13 | val typingModules = modules.filterIsInstance(ModularAdapter.Module.Typing::class.java)
14 | return typingModules.indexOf(this@TypedVHCreatorMod)
15 | }
16 |
17 | override fun onGetItemType(adapter: ModularAdapter<*>, pos: Int): Int? {
18 | return if (typeResolver.invoke(pos)) adapter.determineOurViewType() else null
19 | }
20 |
21 | override fun onCreateModularVH(
22 | adapter: ModularAdapter,
23 | parent: ViewGroup,
24 | viewType: Int
25 | ): HolderT? {
26 | if (adapter.determineOurViewType() != viewType) return null
27 | return factory.invoke(parent)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/navigation/NavArgsExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.navigation
2 |
3 | import android.os.Bundle
4 | import android.os.Parcelable
5 | import androidx.lifecycle.SavedStateHandle
6 | import androidx.navigation.NavArgs
7 | import androidx.navigation.NavArgsLazy
8 | import java.io.Serializable
9 |
10 | // TODO Remove with "androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha/stable"
11 | inline fun SavedStateHandle.navArgs() = NavArgsLazy(Args::class) {
12 | Bundle().apply {
13 | keys().forEach {
14 | when (val value = get(it)) {
15 | is Serializable -> putSerializable(it, value)
16 | is Parcelable -> putParcelable(it, value)
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/navigation/NavControllerExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.navigation
2 |
3 | import android.os.Bundle
4 | import androidx.annotation.IdRes
5 | import androidx.navigation.NavController
6 | import androidx.navigation.NavDirections
7 |
8 | fun NavController.navigateIfNotThere(@IdRes resId: Int, args: Bundle? = null) {
9 | if (currentDestination?.id == resId) return
10 | navigate(resId, args)
11 | }
12 |
13 | fun NavController.doNavigate(direction: NavDirections) {
14 | currentDestination?.getAction(direction.actionId)?.let { navigate(direction) }
15 | }
16 |
17 | fun NavController.isGraphSet(): Boolean = try {
18 | graph
19 | true
20 | } catch (e: IllegalStateException) {
21 | false
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/navigation/NavDestinationExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.navigation
2 |
3 | import androidx.annotation.IdRes
4 | import androidx.navigation.NavDestination
5 |
6 | fun NavDestination?.hasAction(@IdRes id: Int): Boolean {
7 | if (this == null) return false
8 | return getAction(id) != null
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/navigation/NavDirectionsExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.navigation
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.navigation.NavDirections
5 | import eu.darken.myperm.common.livedata.SingleLiveEvent
6 |
7 | fun NavDirections.navVia(pub: MutableLiveData) = pub.postValue(this)
8 |
9 | fun NavDirections.navVia(provider: NavEventSource) = this.navVia(provider.navEvents)
10 |
11 | interface NavEventSource {
12 | val navEvents: SingleLiveEvent
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/notifications/PendingIntentCompat.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.notifications
2 |
3 | import android.app.PendingIntent
4 | import eu.darken.myperm.common.hasApiLevel
5 |
6 | object PendingIntentCompat {
7 | val FLAG_IMMUTABLE: Int = if (hasApiLevel(31)) {
8 | PendingIntent.FLAG_IMMUTABLE
9 | } else {
10 | 0
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/preferences/FlowPreferenceExtension.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.preferences
2 |
3 | import android.content.SharedPreferences
4 |
5 |
6 | inline fun basicReader(defaultValue: T): (rawValue: Any?) -> T =
7 | { rawValue ->
8 | (rawValue ?: defaultValue) as T
9 | }
10 |
11 | inline fun basicWriter(): (T) -> Any? =
12 | { value ->
13 | when (value) {
14 | is Boolean -> value
15 | is String -> value
16 | is Int -> value
17 | is Long -> value
18 | is Float -> value
19 | null -> null
20 | else -> throw NotImplementedError()
21 | }
22 | }
23 |
24 | inline fun SharedPreferences.createFlowPreference(
25 | key: String,
26 | defaultValue: T = null as T
27 | ) = FlowPreference(
28 | preferences = this,
29 | key = key,
30 | rawReader = basicReader(defaultValue),
31 | rawWriter = basicWriter()
32 | )
33 |
34 | inline fun SharedPreferences.createFlowPreference(
35 | key: String,
36 | noinline reader: (rawValue: Any?) -> T,
37 | noinline writer: (value: T) -> Any?,
38 | ) = FlowPreference(
39 | preferences = this,
40 | key = key,
41 | rawReader = reader,
42 | rawWriter = writer,
43 | )
44 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/preferences/Settings.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.preferences
2 |
3 | import android.content.SharedPreferences
4 | import androidx.preference.PreferenceDataStore
5 |
6 | abstract class Settings {
7 |
8 | abstract val preferenceDataStore: PreferenceDataStore
9 |
10 | abstract val preferences: SharedPreferences
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/preferences/SharedPreferenceExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.preferences
2 |
3 | import android.content.SharedPreferences
4 | import androidx.core.content.edit
5 | import eu.darken.myperm.common.debug.logging.Logging.Priority.VERBOSE
6 | import eu.darken.myperm.common.debug.logging.log
7 |
8 | fun SharedPreferences.clearAndNotify() {
9 | val currentKeys = this.all.keys.toSet()
10 | log(VERBOSE) { "$this clearAndNotify(): $currentKeys" }
11 | edit {
12 | currentKeys.forEach { remove(it) }
13 | }
14 | // Clear does not notify anyone using registerOnSharedPreferenceChangeListener
15 | edit(commit = true) { clear() }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/serialization/MoshiExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.serialization
2 |
3 | import com.squareup.moshi.JsonDataException
4 | import com.squareup.moshi.JsonReader
5 | import com.squareup.moshi.Moshi
6 | import com.squareup.moshi.adapter
7 | import okio.Buffer
8 | import okio.BufferedSource
9 |
10 | inline fun Moshi.fromJson(json: String): T =
11 | fromJson(Buffer().writeUtf8(json))
12 |
13 | inline fun Moshi.fromJson(source: BufferedSource): T =
14 | fromJson(JsonReader.of(source))
15 |
16 | inline fun Moshi.fromJson(reader: JsonReader): T =
17 | adapter().fromJson(reader) ?: throw JsonDataException()
18 |
19 | inline fun Moshi.toJson(value: T): String =
20 | adapter().toJson(value)
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/serialization/SerializationModule.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.serialization
2 |
3 | import com.squareup.moshi.Moshi
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import javax.inject.Singleton
9 |
10 | @InstallIn(SingletonComponent::class)
11 | @Module
12 | class SerializationModule {
13 |
14 | @Provides
15 | @Singleton
16 | fun moshi(): Moshi = Moshi.Builder().apply {
17 |
18 | }.build()
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/uix/ViewModel1.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.uix
2 |
3 | import androidx.annotation.CallSuper
4 | import androidx.lifecycle.ViewModel
5 | import eu.darken.myperm.common.debug.logging.log
6 | import eu.darken.myperm.common.debug.logging.logTag
7 |
8 | abstract class ViewModel1 : ViewModel() {
9 | val TAG: String = logTag("VM", javaClass.simpleName)
10 |
11 | init {
12 | log(TAG) { "Initialized" }
13 | }
14 |
15 | @CallSuper
16 | override fun onCleared() {
17 | log(TAG) { "onCleared()" }
18 | super.onCleared()
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/uix/ViewModel3.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.uix
2 |
3 | import androidx.navigation.NavDirections
4 | import eu.darken.myperm.common.coroutine.DispatcherProvider
5 | import eu.darken.myperm.common.debug.logging.asLog
6 | import eu.darken.myperm.common.debug.logging.log
7 | import eu.darken.myperm.common.error.ErrorEventSource
8 | import eu.darken.myperm.common.flow.setupCommonEventHandlers
9 | import eu.darken.myperm.common.livedata.SingleLiveEvent
10 | import eu.darken.myperm.common.navigation.NavEventSource
11 | import eu.darken.myperm.common.navigation.navVia
12 | import kotlinx.coroutines.CoroutineExceptionHandler
13 | import kotlinx.coroutines.flow.Flow
14 | import kotlinx.coroutines.flow.launchIn
15 |
16 |
17 | abstract class ViewModel3(
18 | dispatcherProvider: DispatcherProvider,
19 | ) : ViewModel2(dispatcherProvider), NavEventSource, ErrorEventSource {
20 |
21 | override val navEvents = SingleLiveEvent()
22 | override val errorEvents = SingleLiveEvent()
23 |
24 | init {
25 | launchErrorHandler = CoroutineExceptionHandler { _, ex ->
26 | log(TAG) { "Error during launch: ${ex.asLog()}" }
27 | errorEvents.postValue(ex)
28 | }
29 | }
30 |
31 | override fun Flow.launchInViewModel() = this
32 | .setupCommonEventHandlers(TAG) { "launchInViewModel()" }
33 | .launchIn(vmScope)
34 |
35 | fun NavDirections.navigate() {
36 | navVia(navEvents)
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/upgrade/UpgradeRepo.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade
2 |
3 | import android.app.Activity
4 | import kotlinx.coroutines.flow.Flow
5 | import java.time.Instant
6 |
7 | interface UpgradeRepo {
8 | val upgradeInfo: Flow
9 |
10 | fun launchBillingFlow(activity: Activity)
11 |
12 | interface Info {
13 | val type: Type
14 |
15 | val isPro: Boolean
16 |
17 | val upgradedAt: Instant?
18 | }
19 |
20 | enum class Type {
21 | GPLAY,
22 | FOSS
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/upgrade/UpgradeRepoExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.upgrade
2 |
3 | import kotlinx.coroutines.flow.first
4 |
5 |
6 | suspend fun UpgradeRepo.isPro(): Boolean = upgradeInfo.first().isPro
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/common/worker/WorkerExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.common.worker
2 |
3 | //import android.os.Parcel
4 | //import android.os.Parcelable
5 | //import androidx.work.Data
6 | //
7 | //@Suppress("UNCHECKED_CAST")
8 | //inline fun Data.getParcelable(key: String): T? {
9 | // val parcel = Parcel.obtain()
10 | // try {
11 | // val bytes = getByteArray(key) ?: return null
12 | // parcel.unmarshall(bytes, 0, bytes.size)
13 | // parcel.setDataPosition(0)
14 | // val creator = T::class.java.getField("CREATOR").get(null) as Parcelable.Creator
15 | // return creator.createFromParcel(parcel)
16 | // } finally {
17 | // parcel.recycle()
18 | // }
19 | //}
20 | //
21 | //
22 | //fun Data.Builder.putParcelable(key: String, parcelable: Parcelable): Data.Builder {
23 | // val parcel = Parcel.obtain()
24 | // try {
25 | // parcelable.writeToParcel(parcel, 0)
26 | // putByteArray(key, parcel.marshall())
27 | // } finally {
28 | // parcel.recycle()
29 | // }
30 | // return this
31 | //}
32 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/main/ui/onboarding/OnboardingFragment.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.main.ui.onboarding
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.viewModels
6 | import androidx.navigation.findNavController
7 | import dagger.hilt.android.AndroidEntryPoint
8 | import eu.darken.myperm.R
9 | import eu.darken.myperm.common.PrivacyPolicy
10 | import eu.darken.myperm.common.WebpageTool
11 | import eu.darken.myperm.common.uix.Fragment3
12 | import eu.darken.myperm.common.viewbinding.viewBinding
13 | import eu.darken.myperm.databinding.OnboardingFragmentBinding
14 | import javax.inject.Inject
15 |
16 |
17 | @AndroidEntryPoint
18 | class OnboardingFragment : Fragment3(R.layout.onboarding_fragment) {
19 |
20 | override val vm: OnboardingFragmentVM by viewModels()
21 | override val ui: OnboardingFragmentBinding by viewBinding()
22 | @Inject lateinit var webpageTool: WebpageTool
23 |
24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
25 | customNavController = requireActivity().findNavController(R.id.nav_host_main_activity)
26 | ui.goPrivacyPolicy.setOnClickListener { webpageTool.open(PrivacyPolicy.URL) }
27 | ui.continueAction.setOnClickListener { vm.finishOnboarding() }
28 | super.onViewCreated(view, savedInstanceState)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/main/ui/onboarding/OnboardingFragmentVM.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.main.ui.onboarding
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import eu.darken.myperm.common.coroutine.DispatcherProvider
6 | import eu.darken.myperm.common.uix.ViewModel3
7 | import eu.darken.myperm.settings.core.GeneralSettings
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class OnboardingFragmentVM @Inject constructor(
12 | @Suppress("UNUSED_PARAMETER") handle: SavedStateHandle,
13 | dispatcherProvider: DispatcherProvider,
14 | private val generalSettings: GeneralSettings,
15 | ) : ViewModel3(dispatcherProvider = dispatcherProvider) {
16 |
17 | fun finishOnboarding() {
18 | generalSettings.isOnboardingFinished.value = true
19 | OnboardingFragmentDirections.actionOnboardingFragmentToMainFragment().navigate()
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/main/ui/overview/PkgCount.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.main.ui.overview
2 |
3 | import android.content.Context
4 | import eu.darken.myperm.R
5 | import eu.darken.myperm.common.getQuantityString
6 |
7 | data class PkgCount(
8 | val user: Int,
9 | val system: Int,
10 | ) {
11 | fun getHR(c: Context): String {
12 | val sumText = c.getQuantityString(R.plurals.generic_x_apps_label, user + system)
13 | val userText = c.getQuantityString(R.plurals.generic_x_apps_user_label, user)
14 | val systemText = c.getQuantityString(R.plurals.generic_x_apps_system_label, system)
15 | return "$sumText ($userText, $systemText)"
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/PermissionGroup.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import android.os.Parcelable
6 | import androidx.core.content.ContextCompat
7 | import eu.darken.myperm.permissions.core.known.APermGrp
8 | import kotlinx.parcelize.Parcelize
9 |
10 | interface PermissionGroup {
11 | val id: Id
12 |
13 | fun getLabel(context: Context): String? {
14 | APermGrp.values.singleOrNull { it.id == id }
15 | ?.labelRes
16 | ?.let { return context.getString(it) }
17 | return null
18 | }
19 |
20 | fun getDescription(context: Context): String? {
21 | APermGrp.values.singleOrNull { it.id == id }
22 | ?.descriptionRes
23 | ?.let { return context.getString(it) }
24 |
25 | return null
26 | }
27 |
28 | fun getIcon(context: Context): Drawable? {
29 | APermGrp.values.singleOrNull { it.id == id }
30 | ?.iconRes
31 | ?.let { ContextCompat.getDrawable(context, it) }
32 | ?.let { return it }
33 |
34 | return null
35 | }
36 |
37 | @Parcelize
38 | data class Id(val value: String) : Parcelable
39 |
40 | }
41 |
42 | fun grpIds(vararg groups: PermissionGroup): Set = groups.map { it.id }.toSet()
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/PermissionRepoExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core
2 |
3 | import eu.darken.myperm.permissions.core.container.BasePermission
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.filterIsInstance
6 | import kotlinx.coroutines.flow.map
7 |
8 | val PermissionRepo.permissions: Flow>
9 | get() = state.filterIsInstance().map { it.permissions }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/container/BasePermission.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core.container
2 |
3 | import eu.darken.myperm.apps.core.container.BasePkg
4 | import eu.darken.myperm.permissions.core.Permission
5 |
6 | sealed class BasePermission : Permission {
7 |
8 | abstract val requestingPkgs: Collection
9 | abstract val grantingPkgs: Collection
10 | abstract val declaringPkgs: Collection
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/container/DeclaredPermission.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core.container
2 |
3 | import android.content.pm.PermissionInfo
4 | import eu.darken.myperm.apps.core.container.BasePkg
5 | import eu.darken.myperm.apps.core.features.getPermission
6 | import eu.darken.myperm.apps.core.features.isGranted
7 | import eu.darken.myperm.apps.core.features.requestsPermission
8 | import eu.darken.myperm.permissions.core.*
9 | import eu.darken.myperm.permissions.core.features.PermissionTag
10 |
11 |
12 | data class DeclaredPermission(
13 | val permissionInfo: PermissionInfo,
14 | override val requestingPkgs: List = emptyList(),
15 | override val declaringPkgs: Collection = emptyList(),
16 | override val tags: Collection,
17 | override val groupIds: Collection,
18 | ) : BasePermission() {
19 |
20 | override val id: Permission.Id
21 | get() = Permission.Id(permissionInfo.name)
22 |
23 | override val grantingPkgs: Collection by lazy {
24 | requestingPkgs
25 | .filter { it.requestsPermission(this) }
26 | .filter { it.getPermission(id)?.isGranted == true }
27 | }
28 |
29 | val protectionType: ProtectionType by lazy { permissionInfo.protectionTypeCompat }
30 |
31 | val protectionFlags: Set by lazy { permissionInfo.protectionFlagsCompat }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/container/ExtraPermission.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core.container
2 |
3 | import eu.darken.myperm.apps.core.container.BasePkg
4 | import eu.darken.myperm.apps.core.features.getPermission
5 | import eu.darken.myperm.apps.core.features.isGranted
6 | import eu.darken.myperm.apps.core.features.requestsPermission
7 | import eu.darken.myperm.permissions.core.Permission
8 | import eu.darken.myperm.permissions.core.PermissionGroup
9 | import eu.darken.myperm.permissions.core.features.PermissionTag
10 |
11 |
12 | data class ExtraPermission(
13 | override val id: Permission.Id,
14 | override val requestingPkgs: List = emptyList(),
15 | override val declaringPkgs: Collection = emptyList(),
16 | override val tags: Collection,
17 | override val groupIds: Collection,
18 | ) : BasePermission() {
19 |
20 | override val grantingPkgs: Collection by lazy {
21 | requestingPkgs
22 | .filter { it.requestsPermission(this) }
23 | .filter { it.getPermission(id)?.isGranted == true }
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/container/UnknownPermission.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core.container
2 |
3 | import eu.darken.myperm.apps.core.container.BasePkg
4 | import eu.darken.myperm.apps.core.features.getPermission
5 | import eu.darken.myperm.apps.core.features.isGranted
6 | import eu.darken.myperm.apps.core.features.requestsPermission
7 | import eu.darken.myperm.permissions.core.Permission
8 | import eu.darken.myperm.permissions.core.PermissionGroup
9 | import eu.darken.myperm.permissions.core.features.PermissionTag
10 |
11 | data class UnknownPermission(
12 | override val id: Permission.Id,
13 | override val requestingPkgs: List = emptyList(),
14 | override val tags: Collection,
15 | override val groupIds: Collection,
16 | ) : BasePermission() {
17 |
18 | override val grantingPkgs: Collection by lazy {
19 | requestingPkgs
20 | .filter { it.requestsPermission(this) }
21 | .filter { it.getPermission(id)?.isGranted == true }
22 | }
23 |
24 | override val declaringPkgs: Collection
25 | get() = emptyList()
26 |
27 | override fun toString(): String = "NormalPermission($id)"
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/core/features/PermissionTags.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.core.features
2 |
3 | sealed class PermissionTag
4 |
5 | object ManifestDoc : PermissionTag()
6 |
7 | object RuntimeGrant : PermissionTag()
8 |
9 | object InstallTimeGrant : PermissionTag()
10 |
11 | object SpecialAccess : PermissionTag()
12 |
13 | object Highlighted : PermissionTag()
14 |
15 | object NotNormalPerm : PermissionTag()
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/ui/details/PermissionDetailsEvents.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.ui.details
2 |
3 | import eu.darken.myperm.apps.core.Pkg
4 | import eu.darken.myperm.permissions.core.features.PermissionAction
5 |
6 | sealed class PermissionDetailsEvents {
7 | data class ShowAppSystemDetails(val pkg: Pkg) : PermissionDetailsEvents()
8 | data class PermissionEvent(val permAction: PermissionAction) : PermissionDetailsEvents()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/ui/list/PermissionListEvent.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.ui.list
2 |
3 | import eu.darken.myperm.permissions.core.features.PermissionAction
4 |
5 | sealed class PermissionListEvent {
6 | data class ShowFilterDialog(val options: PermsFilterOptions) : PermissionListEvent()
7 |
8 | data class ShowSortDialog(val options: PermsSortOptions) : PermissionListEvent()
9 |
10 | data class PermissionEvent(val permAction: PermissionAction) : PermissionListEvent()
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/ui/list/SortDialog.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.ui.list
2 |
3 | import android.app.Activity
4 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
5 | import eu.darken.myperm.R
6 | import eu.darken.myperm.common.dialog.BaseDialogBuilder
7 |
8 | class SortDialog(private val activity: Activity) : BaseDialogBuilder(activity) {
9 |
10 | fun show(
11 | options: PermsSortOptions,
12 | onResult: (PermsSortOptions) -> Unit
13 | ) {
14 | val itemLabels = PermsSortOptions.Sort.values().map {
15 | getString(it.labelRes)
16 | }.toTypedArray()
17 |
18 | var checkedItem = PermsSortOptions.Sort.values().indexOf(options.mainSort)
19 |
20 | MaterialAlertDialogBuilder(context).apply {
21 | setTitle(R.string.general_sort_action)
22 |
23 | setSingleChoiceItems(itemLabels, checkedItem) { dialog, which ->
24 | val new = options.copy(
25 | mainSort = PermsSortOptions.Sort.values()[which]
26 | )
27 | onResult(new)
28 | dialog.dismiss()
29 | }
30 | }.show()
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/ui/list/groups/PermissionGroupItem.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.ui.list.groups
2 |
3 | import eu.darken.myperm.permissions.core.PermissionGroup
4 | import eu.darken.myperm.permissions.ui.list.PermissionsAdapter
5 |
6 | interface PermissionGroupItem : PermissionsAdapter.Item {
7 | val groupId: PermissionGroup.Id
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/permissions/ui/list/permissions/PermissionItem.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.permissions.ui.list.permissions
2 |
3 | import eu.darken.myperm.permissions.core.Permission
4 | import eu.darken.myperm.permissions.ui.list.PermissionsAdapter
5 |
6 | sealed class PermissionItem : PermissionsAdapter.Item {
7 | abstract val permission: Permission
8 |
9 | val permissionId: Permission.Id
10 | get() = permission.id
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/settings/ui/SettingsFragmentVM.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.settings.ui
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import eu.darken.myperm.common.coroutine.DispatcherProvider
6 | import eu.darken.myperm.common.uix.ViewModel2
7 | import javax.inject.Inject
8 |
9 | @HiltViewModel
10 | class SettingsFragmentVM @Inject constructor(
11 | private val handle: SavedStateHandle,
12 | private val dispatcherProvider: DispatcherProvider,
13 | ) : ViewModel2(dispatcherProvider)
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/settings/ui/acks/AcknowledgementsFragment.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.settings.ui.acks
2 |
3 | import androidx.annotation.Keep
4 | import androidx.fragment.app.viewModels
5 | import dagger.hilt.android.AndroidEntryPoint
6 | import eu.darken.myperm.R
7 | import eu.darken.myperm.common.uix.PreferenceFragment2
8 | import eu.darken.myperm.settings.core.GeneralSettings
9 | import javax.inject.Inject
10 |
11 | @Keep
12 | @AndroidEntryPoint
13 | class AcknowledgementsFragment : PreferenceFragment2() {
14 |
15 | private val vm: AcknowledgementsFragmentVM by viewModels()
16 |
17 | override val preferenceFile: Int = R.xml.preferences_acknowledgements
18 | @Inject lateinit var debugSettings: GeneralSettings
19 |
20 | override val settings: GeneralSettings by lazy { debugSettings }
21 |
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/settings/ui/acks/AcknowledgementsFragmentVM.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.settings.ui.acks
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import dagger.assisted.AssistedInject
5 | import eu.darken.myperm.common.coroutine.DispatcherProvider
6 | import eu.darken.myperm.common.debug.logging.logTag
7 | import eu.darken.myperm.common.uix.ViewModel3
8 |
9 | class AcknowledgementsFragmentVM @AssistedInject constructor(
10 | private val handle: SavedStateHandle,
11 | private val dispatcherProvider: DispatcherProvider
12 | ) : ViewModel3(dispatcherProvider) {
13 |
14 | companion object {
15 | private val TAG = logTag("Settings", "Acknowledgements", "VM")
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/settings/ui/general/GeneralSettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.settings.ui.general
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.annotation.Keep
6 | import androidx.fragment.app.viewModels
7 | import androidx.preference.CheckBoxPreference
8 | import dagger.hilt.android.AndroidEntryPoint
9 | import eu.darken.myperm.R
10 | import eu.darken.myperm.common.uix.PreferenceFragment2
11 | import eu.darken.myperm.settings.core.GeneralSettings
12 | import javax.inject.Inject
13 |
14 | @Keep
15 | @AndroidEntryPoint
16 | class GeneralSettingsFragment : PreferenceFragment2() {
17 |
18 | private val vm: GeneralSettingsFragmentVM by viewModels()
19 |
20 | @Inject lateinit var debugSettings: GeneralSettings
21 |
22 | override val settings: GeneralSettings by lazy { debugSettings }
23 | override val preferenceFile: Int = R.xml.preferences_general
24 |
25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
26 | vm.isAutoReporting.observe2 {
27 | findPreference("core.bugtracking.enabled")?.isChecked = it
28 | }
29 | super.onViewCreated(view, savedInstanceState)
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/settings/ui/general/GeneralSettingsFragmentVM.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.settings.ui.general
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import eu.darken.myperm.common.coroutine.DispatcherProvider
6 | import eu.darken.myperm.common.debug.autoreport.DebugSettings
7 | import eu.darken.myperm.common.debug.logging.logTag
8 | import eu.darken.myperm.common.uix.ViewModel3
9 | import javax.inject.Inject
10 |
11 | @HiltViewModel
12 | class GeneralSettingsFragmentVM @Inject constructor(
13 | private val handle: SavedStateHandle,
14 | private val dispatcherProvider: DispatcherProvider,
15 | private val debugSettings: DebugSettings,
16 | ) : ViewModel3(dispatcherProvider) {
17 |
18 | val isAutoReporting = debugSettings.isAutoReportingEnabled.flow.asLiveData2()
19 |
20 | companion object {
21 | private val TAG = logTag("Settings", "General", "VM")
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/darken/myperm/settings/ui/support/SupportFragmentVM.kt:
--------------------------------------------------------------------------------
1 | package eu.darken.myperm.settings.ui.support
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import eu.darken.myperm.common.coroutine.DispatcherProvider
6 | import eu.darken.myperm.common.debug.logging.log
7 | import eu.darken.myperm.common.debug.recording.core.RecorderModule
8 | import eu.darken.myperm.common.uix.ViewModel3
9 | import kotlinx.coroutines.flow.map
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class SupportFragmentVM @Inject constructor(
14 | @Suppress("unused") private val handle: SavedStateHandle,
15 | dispatcherProvider: DispatcherProvider,
16 | private val recorderModule: RecorderModule,
17 | ) : ViewModel3(dispatcherProvider) {
18 |
19 | val isRecording = recorderModule.state.map { it.isRecording }.asLiveData2()
20 |
21 | val isRecorderAvailable = recorderModule.state.map { it.isAvailable }.asLiveData2()
22 |
23 | fun startDebugLog() = launch {
24 | log { "startDebugLog()" }
25 | recorderModule.startRecorder()
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_mascot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/drawable-xxxhdpi/ic_mascot.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/app_tag_box_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access_background_location_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access_media_location_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access_notification_policy_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access_notifications_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access_to_media_only_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_accessibility_new_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_apps_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_battery_charging_full_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_battery_unknown_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_bluetooth_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_bug_report_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_call_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_camera_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_check_circle_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_coffee_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_contact_support_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_contacts_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_directions_run_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_edit_calendar_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_expand_less_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_expand_more_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_filter_list_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_gplay_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_group_work_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_heart_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_id_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_install_mobile_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_internet_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_local_phone_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_location_on_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_mic_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_more_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_phone_android_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_picture_in_picture_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_privacy_tip_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_question_mark_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_refresh_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_remove_circle_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_sd_storage_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_security_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_sensors_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_settings_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_settings_applications_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_shield_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_signal_wifi_4_bar_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_signal_wifi_connected_no_internet_4_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_signal_wifi_statusbar_connected_no_internet_4_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_sms_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_sort_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_source_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_stars_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_start_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_user_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_vibration_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_work_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bluetooth_advertise_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_body_sensors_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_card_text_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_change_network_state_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_change_wifi_state_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_changelog_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_connectivity_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_default_app_icon_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_discord_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_email_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_foreground_service_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_get_accounts_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_github_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_heart.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_id_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location_coarse_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location_fine_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_manage_accounts_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_manage_media_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_modify_system_settings_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_network_state_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_nfc_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_query_all_packages_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_read_sync_settings_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_reboot_permission_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_schedule_exact_alarm_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_spider_thread_onsurface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_system_alert_window_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_unlimited_data_access_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_usage_data_access_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_wifi_state_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/launch_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | -
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/overview_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_fragment.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/some_item_line.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_navigation_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_app_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_settings_index.xml:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/bottom_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
13 |
17 |
18 |
22 |
26 |
27 |
31 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences_advanced.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences_general.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/testhelper/BaseTest.kt:
--------------------------------------------------------------------------------
1 | package testhelper
2 |
3 | import eu.darken.myperm.common.debug.logging.Logging
4 | import eu.darken.myperm.common.debug.logging.Logging.Priority.VERBOSE
5 | import eu.darken.myperm.common.debug.logging.log
6 | import io.mockk.unmockkAll
7 | import org.junit.jupiter.api.AfterAll
8 | import testhelpers.logging.JUnitLogger
9 |
10 |
11 | open class BaseTest {
12 | init {
13 | Logging.clearAll()
14 | Logging.install(JUnitLogger())
15 | testClassName = this.javaClass.simpleName
16 | }
17 |
18 | companion object {
19 | private var testClassName: String? = null
20 |
21 | @JvmStatic
22 | @AfterAll
23 | fun onTestClassFinished() {
24 | unmockkAll()
25 | log(testClassName!!, VERBOSE) { "onTestClassFinished()" }
26 | Logging.clearAll()
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/test/java/testhelper/coroutine/TestDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package testhelper.coroutine
2 |
3 | import eu.darken.myperm.common.coroutine.DispatcherProvider
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | class TestDispatcherProvider(private val context: CoroutineDispatcher? = null) : DispatcherProvider {
8 | override val Default: CoroutineDispatcher
9 | get() = context ?: Dispatchers.Unconfined
10 | override val Main: CoroutineDispatcher
11 | get() = context ?: Dispatchers.Unconfined
12 | override val MainImmediate: CoroutineDispatcher
13 | get() = context ?: Dispatchers.Unconfined
14 | override val Unconfined: CoroutineDispatcher
15 | get() = context ?: Dispatchers.Unconfined
16 | override val IO: CoroutineDispatcher
17 | get() = context ?: Dispatchers.Unconfined
18 | }
--------------------------------------------------------------------------------
/app/src/test/java/testhelper/coroutine/TestExtensions.kt:
--------------------------------------------------------------------------------
1 | package testhelper.coroutine
2 |
3 | import eu.darken.myperm.common.debug.logging.asLog
4 | import kotlinx.coroutines.CancellationException
5 | import kotlinx.coroutines.cancel
6 | import kotlinx.coroutines.test.TestScope
7 | import kotlinx.coroutines.test.runTest
8 | import kotlin.coroutines.CoroutineContext
9 | import kotlin.coroutines.EmptyCoroutineContext
10 | import kotlin.reflect.KClass
11 |
12 | fun runTest2(
13 | autoCancel: Boolean = false,
14 | context: CoroutineContext = EmptyCoroutineContext,
15 | expectedError: KClass? = null,
16 | testBody: suspend TestScope.() -> Unit
17 | ) {
18 | try {
19 | val scope = TestScope(context = context)
20 | try {
21 | scope.runTest {
22 | testBody()
23 | if (autoCancel) scope.cancel("autoCancel")
24 | }
25 | } catch (e: Throwable) {
26 | val isExpected = expectedError?.isInstance(e) ?: false
27 | if (!isExpected) throw e
28 | }
29 | } catch (e: CancellationException) {
30 | if (e.message == "autoCancel" && autoCancel) {
31 | io.kotest.mpp.log { "Test was auto-cancelled ${e.asLog()}" }
32 | } else {
33 | throw e
34 | }
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/app/src/test/java/testhelper/json/JsonExtensions.kt:
--------------------------------------------------------------------------------
1 | package testhelper.json
2 |
3 | import com.squareup.moshi.JsonReader
4 | import com.squareup.moshi.Moshi
5 | import okio.Buffer
6 | import okio.ByteString.Companion.encode
7 | import okio.buffer
8 | import okio.sink
9 | import java.io.File
10 |
11 |
12 | fun String.toComparableJson(): String {
13 | val value = Buffer().use {
14 | it.writeUtf8(this)
15 | val reader = JsonReader.of(it)
16 | reader.readJsonValue()
17 | }
18 |
19 | val adapter = Moshi.Builder().build().adapter(Any::class.java).indent(" ")
20 |
21 | return adapter.toJson(value)
22 | }
23 |
24 | fun String.writeToFile(file: File) = encode().let { text ->
25 | require(!file.exists())
26 | file.parentFile?.mkdirs()
27 | file.createNewFile()
28 | file.sink().buffer().use { it.write(text) }
29 | }
--------------------------------------------------------------------------------
/app/src/test/java/testhelper/livedata/InstantExecutorExtension.kt:
--------------------------------------------------------------------------------
1 | package testhelper.livedata
2 |
3 | import androidx.arch.core.executor.ArchTaskExecutor
4 | import androidx.arch.core.executor.TaskExecutor
5 | import org.junit.jupiter.api.extension.AfterEachCallback
6 | import org.junit.jupiter.api.extension.BeforeEachCallback
7 | import org.junit.jupiter.api.extension.ExtensionContext
8 |
9 | class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
10 |
11 | override fun beforeEach(context: ExtensionContext?) {
12 | ArchTaskExecutor.getInstance().setDelegate(
13 | object : TaskExecutor() {
14 | override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
15 |
16 | override fun postToMainThread(runnable: Runnable) = runnable.run()
17 |
18 | override fun isMainThread(): Boolean = true
19 | }
20 | )
21 | }
22 |
23 | override fun afterEach(context: ExtensionContext?) {
24 | ArchTaskExecutor.getInstance().setDelegate(null)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/test/java/testhelper/preferences/MockSharedPreferencesTest.kt:
--------------------------------------------------------------------------------
1 | package testhelper.preferences
2 |
3 | import androidx.core.content.edit
4 | import io.kotest.matchers.shouldBe
5 | import org.junit.jupiter.api.Test
6 | import testhelper.BaseTest
7 | import testhelpers.preferences.MockSharedPreferences
8 |
9 | class MockSharedPreferencesTest : BaseTest() {
10 |
11 | private fun createInstance() = MockSharedPreferences()
12 |
13 | @Test
14 | fun `test boolean insertion`() {
15 | val prefs = createInstance()
16 | prefs.dataMapPeek shouldBe emptyMap()
17 | prefs.getBoolean("key", true) shouldBe true
18 | prefs.edit { putBoolean("key", false) }
19 | prefs.getBoolean("key", true) shouldBe false
20 | prefs.dataMapPeek["key"] shouldBe false
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/testShared/java/testhelpers/BaseTestInstrumentation.kt:
--------------------------------------------------------------------------------
1 | package testhelpers
2 |
3 | import eu.darken.myperm.common.debug.logging.Logging
4 | import eu.darken.myperm.common.debug.logging.Logging.Priority.VERBOSE
5 | import eu.darken.myperm.common.debug.logging.log
6 | import io.mockk.unmockkAll
7 | import org.junit.AfterClass
8 | import testhelpers.logging.JUnitLogger
9 |
10 | abstract class BaseTestInstrumentation {
11 |
12 | init {
13 | Logging.clearAll()
14 | Logging.install(JUnitLogger())
15 | testClassName = this.javaClass.simpleName
16 | }
17 |
18 | companion object {
19 | private var testClassName: String? = null
20 |
21 | @JvmStatic
22 | @AfterClass
23 | fun onTestClassFinished() {
24 | unmockkAll()
25 | log(testClassName!!, VERBOSE) { "onTestClassFinished()" }
26 | Logging.clearAll()
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/testShared/java/testhelpers/IsAUnitTest.kt:
--------------------------------------------------------------------------------
1 | package testhelpers
2 |
3 | class IsAUnitTest
4 |
--------------------------------------------------------------------------------
/app/src/testShared/java/testhelpers/logging/JUnitLogger.kt:
--------------------------------------------------------------------------------
1 | package testhelpers.logging
2 |
3 | import eu.darken.myperm.common.debug.logging.Logging
4 |
5 | class JUnitLogger(private val minLogLevel: Logging.Priority = Logging.Priority.VERBOSE) : Logging.Logger {
6 |
7 | override fun isLoggable(priority: Logging.Priority): Boolean = priority.intValue >= minLogLevel.intValue
8 |
9 | override fun log(priority: Logging.Priority, tag: String, message: String, metaData: Map?) {
10 | println("${System.currentTimeMillis()} ${priority.shortLabel}/$tag: $message")
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/testShared/java/testhelpers/preferences/MockFlowPreference.kt:
--------------------------------------------------------------------------------
1 | package testhelpers.preferences
2 |
3 | import eu.darken.myperm.common.preferences.FlowPreference
4 | import io.mockk.every
5 | import io.mockk.mockk
6 | import kotlinx.coroutines.flow.MutableStateFlow
7 |
8 | fun mockFlowPreference(
9 | defaultValue: T
10 | ): FlowPreference {
11 | val instance = mockk>()
12 | val flow = MutableStateFlow(defaultValue)
13 | every { instance.flow } answers { flow }
14 | every { instance.value } answers { flow.value }
15 | every { instance.update(any()) } answers {
16 | val updateCall = arg<(T) -> T>(0)
17 | flow.value = updateCall(flow.value)
18 | }
19 |
20 | return instance
21 | }
22 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath("com.android.tools.build:gradle:8.7.3")
8 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin.core}")
9 | classpath("com.google.dagger:hilt-android-gradle-plugin:${Versions.Dagger.core}")
10 | classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.AndroidX.Navigation.core}")
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | tasks.register("clean").configure {
22 | delete("build")
23 | }
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | build/
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | `java-library`
4 | }
5 |
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 | dependencies {
11 | implementation("com.android.tools.build:gradle:8.7.3")
12 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24")
13 | implementation("com.squareup:javapoet:1.13.0")
14 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Dependencies.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.artifacts.Dependency
2 | import org.gradle.api.artifacts.dsl.DependencyHandler
3 | import org.gradle.kotlin.dsl.DependencyHandlerScope
4 |
5 | private fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? =
6 | add("implementation", dependencyNotation)
7 |
8 | private fun DependencyHandler.testImplementation(dependencyNotation: Any): Dependency? =
9 | add("testImplementation", dependencyNotation)
10 |
11 | private fun DependencyHandler.kapt(dependencyNotation: Any): Dependency? =
12 | add("kapt", dependencyNotation)
13 |
14 | private fun DependencyHandler.kaptTest(dependencyNotation: Any): Dependency? =
15 | add("kaptTest", dependencyNotation)
16 |
17 | private fun DependencyHandler.androidTestImplementation(dependencyNotation: Any): Dependency? =
18 | add("androidTestImplementation", dependencyNotation)
19 |
20 | private fun DependencyHandler.kaptAndroidTest(dependencyNotation: Any): Dependency? =
21 | add("kaptAndroidTest", dependencyNotation)
22 |
23 | private fun DependencyHandler.`testRuntimeOnly`(dependencyNotation: Any): Dependency? =
24 | add("testRuntimeOnly", dependencyNotation)
25 |
26 | private fun DependencyHandler.`debugImplementation`(dependencyNotation: Any): Dependency? =
27 | add("debugImplementation", dependencyNotation)
28 |
29 | fun DependencyHandlerScope.addSomeDeps() {
30 | //...
31 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Versions.kt:
--------------------------------------------------------------------------------
1 | object Versions {
2 | object Kotlin {
3 | const val core = "1.9.24"
4 | const val coroutines = "1.8.0"
5 | }
6 |
7 | object Dagger {
8 | const val core = "2.51.1"
9 | }
10 |
11 | object AndroidX {
12 | const val core = ""
13 |
14 | object Navigation {
15 | const val core = "2.7.7"
16 | }
17 |
18 | object WorkManager {
19 | const val core = "2.7.1"
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/cs-CZ/short_description.txt:
--------------------------------------------------------------------------------
1 | Přehled oprávnění aplikací pro systém Android
--------------------------------------------------------------------------------
/fastlane/metadata/android/el-GR/short_description.txt:
--------------------------------------------------------------------------------
1 | Επισκόπηση αδειών εφαρμογής Android
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/default.txt:
--------------------------------------------------------------------------------
1 | Bugfixes and performance improvements.
2 | ¯\_(ツ)_/¯
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Permission Pilot is a new kind of app to help you review apps and their permissions.
2 |
3 | With each Android update permissions are getting more complex.
4 | Android showing permissions in various different locations, doesn't make it easier to review them:
5 |
6 | * App Info page
7 | * Special Access
8 | * Permissions Manager
9 | * and more...
10 |
11 | Permission Pilot lists all permissions in a single location, giving you a bird's eye view of app permissions.
12 |
13 | Two perspectives are available: You can either view all permissions an app requests, or view all apps that request a permission.
14 |
15 | Apps tab
16 | All installed apps, including system apps and work profile apps.
17 | Clicking on any app will list all permissions that the app has requested, including those that show up under Permissions Manager and Special Access, along with their status.
18 | This will also include internet permissions, SharedUserID status!
19 |
20 | Permissions tab
21 | All permissions that exist on your device, including those that show up under Permissions Manager and Special Access.
22 | Permissions are pre-grouped for easier navigation, e.g. Contacts, Microphone, Camera, etc.
23 | Clicking on a permission shows all the apps that request access to that permission.
24 |
25 | Apps and permissions can be searched using free-text, sorted and filtered by different criteria.
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot10.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot5.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot6.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot7.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot8.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot9.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Android app permission overview
--------------------------------------------------------------------------------
/fastlane/metadata/android/es-ES/short_description.txt:
--------------------------------------------------------------------------------
1 | Información general sobre los permisos de las aplicaciones en Android
--------------------------------------------------------------------------------
/fastlane/metadata/android/pt-BR/full_description.txt:
--------------------------------------------------------------------------------
1 | Permission Pilot é um novo tipo de ferramenta que ajuda a usuário visualizar quais permissões estão sendo usadas pelos apps - idea surgiu de uma discussão sobre uma funcionalidade para SD Maid.
2 |
3 | Atualmente, o Android mostra permissões de apps em 3 lugares diferentes:
4 |
5 | * Site de informações do app
6 | * Acesso especial
7 | * Gerenciar permissões
8 |
9 | A fim de controlar ou apenas ver a quais permissões um app tem acesso, o usuário tem que ir para 3 lugares diferentes.
10 |
11 | Permission Pilot basicamente listará todas as permissões que um app solicitou em um único lugar, dando ao usuário um insight amplo sobre as permissões de apps. Você verá duas abas:
12 |
13 | * Apps: isto listará todos os apps instalados. Ao tocar em qualquer app você verá todas as permissões que o app solicitou, incluindo aquelas que aparecem em 'Gerenciar permissões' e 'Acesso especial', junto com o seu status (Ligado/ Desligado, Permitido/ Não permitido, etc.)
14 |
15 | * Permissões: isto listará todas as permissões, tais como Contatos, Microfone, Câmera, etc., incluindo as que aparecem em 'Gerenciar permissões' e 'Acesso especial'. Ao tocar em cada um deles, serão exibidos todos os apps que atualmente têm acesso a essa permissão.
16 |
17 | Isso também incluirá permissões de internet, status de ID compartilhada de usuário!
18 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/pt-BR/short_description.txt:
--------------------------------------------------------------------------------
1 | Examine permissões de aplicativos
--------------------------------------------------------------------------------
/fastlane/remove_unsupported_languages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "Removing unsupported languages..."
3 | echo `pwd`
4 | rm -rv ./metadata/android/es-AR
5 | rm -rv ./metadata/android/sc-IT
6 | rm -rv ./metadata/android/sq-AL
7 | rm -rv ./metadata/android/uz
8 | rm -rv ./metadata/android/pcm-NG
9 | rm -rv ./metadata/android/tl-PH
10 | rm -rv ./metadata/android/ku-TR
11 | rm -rv ./metadata/android/kmr-TR
12 | rm -rv ./metadata/android/ur-IN
13 | rm -rv ./metadata/android/zu
14 | rm -rv ./metadata/android/si-LK
15 | find ./metadata/android -empty -type d -delete
16 | exit 0
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | android.defaults.buildfeatures.buildconfig=true
21 | android.nonTransitiveRClass=false
22 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rken-org/permission-pilot/e08a71ef9460f434a7aa884a1294c3495969a37a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jul 13 14:45:28 CEST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "Permission Pilot"
2 | include ':app'
3 |
--------------------------------------------------------------------------------
/version.properties:
--------------------------------------------------------------------------------
1 | ### Updated by release.sh ###
2 | project.versioning.major=1
3 | project.versioning.minor=7
4 | project.versioning.patch=3
5 | project.versioning.build=0
6 | #############################
7 |
--------------------------------------------------------------------------------