├── .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 | 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 | 3 | 8 | 13 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_app_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 18 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_settings_index.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 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 | --------------------------------------------------------------------------------