From 1fe85b61f17c28a2ac7ec9bbdc89a118dc3add82 Mon Sep 17 00:00:00 2001 From: Martin Budinsky Date: Thu, 5 Dec 2024 17:12:22 +0100 Subject: [PATCH] Add mrum-android code This commit adds all of the mrum-android code as a direct copy of the latest feature/next-gen's state from Bitbucket. Caution is required when working over the code as it is a WIP and followup tasks are needed to bring this into a buildable form. --- .editorconfig | 29 + .gitignore | 117 + .run/DocumentationExtractor.run.xml | 24 + .run/Format code.run.xml | 23 + .run/ObfuscationDirector.run.xml | 24 + .run/Publish snapshot.run.xml | 17 + agent/build.gradle.kts | 29 + agent/consumer-rules.pro | 0 agent/lint-baseline.xml | 4 + agent/proguard-rules.pro | 0 app/.gitignore | 1 + app/build.gradle.kts | 171 + app/docker-compose.yaml | 24 + app/keystore.jks | Bin 0 -> 2559 bytes app/otel-collector-config.yaml | 51 + app/src/androidTest/README.md | 19 + .../com/smartlook/app/lib/TestConstants.kt | 25 + .../java/com/smartlook/app/lib/Util.kt | 49 + .../smartlook/app/lib/ZipkinCommunicator.kt | 70 + .../app/screens/HttpUrlConnectionScreen.kt | 48 + .../com/smartlook/app/screens/MainScreen.kt | 30 + .../com/smartlook/app/screens/OkHttpScreen.kt | 38 + .../java/com/smartlook/app/screens/Screen.kt | 150 + .../smartlook/app/tests/component/BaseTest.kt | 27 + .../networkrequests/HttpUrlConnectionTest.kt | 62 + .../networkrequests/NetworkRequestsUtil.kt | 43 + .../component/networkrequests/OkHttp3Test.kt | 62 + .../smartlook/app/tests/e2e/BaseTestE2E.kt | 26 + .../networkrequests/NetworkRequestsTest.kt | 53 + app/src/main/AndroidManifest.xml | 77 + app/src/main/java/com/smartlook/app/App.kt | 51 + .../app/bridge/TomasBridgeInterface.kt | 212 ++ .../com/smartlook/app/extension/ContextExt.kt | 30 + .../app/extension/FragmentTransactionExt.kt | 27 + .../com/smartlook/app/extension/PointFExt.kt | 46 + .../com/smartlook/app/extension/ToolbarExt.kt | 27 + .../java/com/smartlook/app/ui/BaseFragment.kt | 56 + .../java/com/smartlook/app/ui/MainActivity.kt | 78 + .../com/smartlook/app/ui/SampleFragment.kt | 58 + .../app/ui/adapter/SensitiveItemAdapter.kt | 55 + .../app/ui/adapter/SimpleItemAdapter.kt | 56 + .../app/ui/adapter/SpinnerAdapter.kt | 47 + .../app/ui/adapter/ViewPagerAdapter.kt | 34 + .../app/ui/adapter/ViewPagerAdapter2.kt | 33 + .../app/ui/compose/CameraComposeActivity.kt | 127 + .../ui/compose/DrawOrderComposeActivity.kt | 98 + .../DrawRecompositionComposeActivity.kt | 121 + .../app/ui/compose/ListComposeActivity.kt | 204 ++ .../MeasureRecompositionComposeActivity.kt | 76 + .../app/ui/compose/SurfaceComposeActivity.kt | 96 + .../ui/compose/TextFieldComposeActivity.kt | 200 ++ .../app/ui/compose/VideoComposeActivity.kt | 84 + .../compose/ViewDrawOrderComposeActivity.kt | 134 + .../app/ui/compose/WebViewComposeActivity.kt | 526 +++ .../smartlook/app/ui/compose/theme/Color.kt | 24 + .../smartlook/app/ui/compose/theme/Theme.kt | 51 + .../smartlook/app/ui/dialog/AlertDialog.kt | 39 + .../ui/dialog/BottomSheetDialogFragment.kt | 42 + .../smartlook/app/ui/dialog/DialogActivity.kt | 36 + .../smartlook/app/ui/dialog/DialogFragment.kt | 50 + .../app/ui/dialog/WindowManagerDialog.kt | 57 + .../app/ui/dialog/WindowManagerToast.kt | 60 + .../HttpURLConnectionFragment.kt | 204 ++ .../app/ui/interaction/FocusActivity.kt | 52 + .../com/smartlook/app/ui/menu/MenuFragment.kt | 256 ++ .../smartlook/app/ui/okhttp/OkHttpFragment.kt | 622 ++++ .../app/ui/screenshot/AnimationFragment.kt | 40 + .../screenshot/ScreenshotRegionsFragment.kt | 90 + .../ui/screenshot/ScreenshotViewsFragment.kt | 115 + .../ui/wireframe/BridgeInterfaceFragment.kt | 52 + .../ui/wireframe/CollapsingLayoutFragment.kt | 41 + .../app/ui/wireframe/EmptyActivity.kt | 37 + .../app/ui/wireframe/ListFragment.kt | 49 + .../app/ui/wireframe/WebViewFragment.kt | 55 + .../ui/wireframe/WireframeViewsFragment.kt | 187 ++ .../wireframe/extend/MyGoogleMapFragment.kt | 21 + .../app/ui/wireframe/extend/MyTextView.kt | 23 + .../app/ui/wireframe/model/SimpleItem.kt | 26 + .../java/com/smartlook/app/util/Common.kt | 26 + .../smartlook/app/util/FragmentAnimation.kt | 39 + .../com/smartlook/app/view/BarChartView.kt | 281 ++ .../com/smartlook/app/view/BrokenSeekBar.kt | 65 + .../smartlook/app/view/CanvasElementsView.kt | 383 +++ .../smartlook/app/view/CanvasTextureView.kt | 80 + .../smartlook/app/view/ColoredLinearLayout.kt | 50 + .../smartlook/app/view/HardwareBitmapView.kt | 97 + .../smartlook/app/view/RuntimeShaderView.kt | 73 + .../com/smartlook/app/view/SequenceView.kt | 86 + .../java/com/smartlook/app/view/ShadowView.kt | 62 + .../smartlook/app/view/SimpleCanvasView.kt | 40 + .../smartlook/app/view/SimpleGLSurfaceView.kt | 304 ++ .../com/smartlook/app/view/TextRunDrawView.kt | 117 + .../smartlook/app/view/bridge/TomasElement.kt | 30 + .../app/view/bridge/TomasTextureView.kt | 163 + .../smartlook/app/view/bridge/TomasView.kt | 46 + app/src/main/res/anim/fade_in.xml | 6 + app/src/main/res/anim/fade_out.xml | 6 + .../main/res/anim/slow_enter_from_left.xml | 10 + .../main/res/anim/slow_enter_from_right.xml | 10 + app/src/main/res/anim/slow_exit_to_left.xml | 10 + app/src/main/res/anim/slow_exit_to_right.xml | 10 + app/src/main/res/animator/slow_fade_in.xml | 9 + app/src/main/res/animator/slow_fade_out.xml | 9 + .../drawable-v24/ic_launcher_foreground.xml | 30 + app/src/main/res/drawable/app_icon.xml | 85 + .../main/res/drawable/bcg_gradient_blue.xml | 6 + app/src/main/res/drawable/ic_add.xml | 9 + app/src/main/res/drawable/ic_back.xml | 12 + app/src/main/res/drawable/ic_bitmap_big.png | Bin 0 -> 15053 bytes .../main/res/drawable/ic_bitmap_sample_1.jpg | Bin 0 -> 28295 bytes .../main/res/drawable/ic_bitmap_sample_2.jpg | Bin 0 -> 37525 bytes .../main/res/drawable/ic_bitmap_sample_3.jpg | Bin 0 -> 69518 bytes .../main/res/drawable/ic_bitmap_sample_4.jpg | Bin 0 -> 21329 bytes .../main/res/drawable/ic_bitmap_sample_5.jpg | Bin 0 -> 22944 bytes .../main/res/drawable/ic_bitmap_sample_6.jpg | Bin 0 -> 39866 bytes app/src/main/res/drawable/ic_bitmap_small.png | Bin 0 -> 1835 bytes app/src/main/res/drawable/ic_crop_test.xml | 17 + app/src/main/res/drawable/ic_gradient.xml | 6 + .../res/drawable/ic_launcher_background.xml | 170 + app/src/main/res/drawable/ic_progress.xml | 14 + app/src/main/res/drawable/ic_select_all.xml | 10 + app/src/main/res/drawable/ic_tab_dot.xml | 21 + app/src/main/res/drawable/ic_test_rect_1.xml | 49 + app/src/main/res/drawable/ic_test_rect_2.xml | 29 + app/src/main/res/drawable/ic_vector_big.xml | 85 + app/src/main/res/drawable/ic_vector_small.xml | 85 + app/src/main/res/drawable/sel_button.xml | 26 + app/src/main/res/drawable/sel_text.xml | 5 + app/src/main/res/layout/activity_empty.xml | 18 + app/src/main/res/layout/activity_main.xml | 26 + .../res/layout/fragment_bridge_interface.xml | 20 + .../res/layout/fragment_collapsing_layout.xml | 67 + app/src/main/res/layout/fragment_dialog.xml | 63 + app/src/main/res/layout/fragment_dummy.xml | 3 + .../layout/fragment_http_url_connection.xml | 124 + app/src/main/res/layout/fragment_menu.xml | 511 +++ app/src/main/res/layout/fragment_okhttp.xml | 148 + .../layout/fragment_screenshot_regions.xml | 964 ++++++ .../res/layout/fragment_screenshot_views.xml | 335 ++ .../main/res/layout/fragment_transactions.xml | 17 + app/src/main/res/layout/fragment_web_view.xml | 37 + .../res/layout/fragment_wireframe_list.xml | 12 + .../res/layout/fragment_wireframe_views.xml | 2884 +++++++++++++++++ app/src/main/res/layout/item_example.xml | 29 + app/src/main/res/layout/item_sensitive.xml | 47 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/raw/rainy_day.mp4 | Bin 0 -> 786151 bytes app/src/main/res/values/attrs.xml | 12 + app/src/main/res/values/colors.xml | 9 + app/src/main/res/values/strings.xml | 97 + app/src/main/res/values/styles.xml | 66 + app/src/main/res/values/themes.xml | 5 + app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + build.gradle.kts | 49 + buildSrc/build.gradle.kts | 14 + buildSrc/src/main/kotlin/Configurations.kt | 36 + buildSrc/src/main/kotlin/Dependencies.kt | 202 ++ buildSrc/src/main/kotlin/Ktlint.kt | 15 + buildSrc/src/main/kotlin/TaskGroups.kt | 4 + .../main/kotlin/plugins/ConfigAndroidApp.kt | 36 + .../kotlin/plugins/ConfigAndroidLibrary.kt | 52 + .../src/main/kotlin/plugins/ConfigKtLint.kt | 49 + .../src/main/kotlin/plugins/ConfigLint.kt | 12 + .../src/main/kotlin/plugins/ConfigPublish.kt | 98 + .../src/main/kotlin/plugins/LocalPlugin.kt | 42 + buildSrc/src/main/kotlin/utils/Extensions.kt | 58 + .../kotlin/utils/ObfuscationTypeAnalyzer.kt | 62 + .../main/kotlin/utils/ProjectProperties.kt | 14 + common/otel/api/build.gradle.kts | 44 + common/otel/api/consumer-rules.pro | 0 common/otel/api/lint-baseline.xml | 4 + common/otel/api/proguard-rules.pro | 1 + common/otel/api/src/main/AndroidManifest.xml | 13 + common/otel/api/src/main/assets/LICENSE.txt | 11 + .../mrum/common/otel/api/OpenTelemetry.kt | 35 + .../otel/api/OpenTelemetryInitializer.kt | 136 + .../api/internal/OfflineOtelDataProcessor.kt | 72 + .../common/otel/api/internal/Resources.kt | 59 + .../api/logRecord/AndroidLogRecordExporter.kt | 99 + .../api/logRecord/UploadOtelLogRecordData.kt | 34 + .../logRecord/UploadOtelLogRecordDataJob.kt | 117 + .../otel/api/span/AndroidSpanExporter.kt | 61 + .../otel/api/span/UploadOtelSpanData.kt | 34 + .../otel/api/span/UploadOtelSpanDataJob.kt | 117 + common/otel/internal/build.gradle.kts | 30 + common/otel/internal/consumer-rules.pro | 0 common/otel/internal/lint-baseline.xml | 4 + common/otel/internal/proguard-rules.pro | 1 + .../otel/internal/src/main/assets/LICENSE.txt | 11 + .../otel/internal/storage/OtelStorage.kt | 54 + gradle.properties | 5 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 + gradlew.bat | 90 + .../build-info-injector/build.gradle.kts | 89 + .../src/main/assets/LICENSE.txt | 11 + .../BuildInfoInjectorPlugin.kt | 134 + .../utils/MappingFileModifier.kt | 82 + .../utils/ObfuscationTypeAnalyzer.kt | 78 + .../networkrequest/bci/build.gradle.kts | 44 + .../bci/src/main/assets/LICENSE.txt | 11 + .../HttpUrlConnectionPlugin.java | 240 ++ .../rum/bci/okhttp3/OkHttpCallbackAdvice.java | 42 + .../rum/bci/okhttp3/OkHttpCallbackPlugin.java | 55 + .../rum/bci/okhttp3/OkHttpClientAdvice.java | 43 + .../rum/bci/okhttp3/OkHttpClientPlugin.java | 55 + .../META-INF/net.bytebuddy/build.plugins | 3 + .../buildtime/plugin/build.gradle.kts | 99 + .../plugin/src/main/assets/LICENSE.txt | 11 + .../plugin/AndroidInstrumentationPlugin.java | 49 + instrumentation/runtime/anr/build.gradle.kts | 35 + .../runtime/anr/consumer-rules.pro | 0 instrumentation/runtime/anr/lint-baseline.xml | 4 + .../runtime/anr/proguard-rules.pro | 1 + .../runtime/anr/src/main/assets/LICENSE.txt | 11 + .../com/cisco/android/rum/anr/ANRWatchDog.kt | 87 + .../android/rum/anr/AnrReportingHandler.kt | 149 + .../cisco/android/rum/anr/ANRWatchDogTest.kt | 118 + .../rum/anr/AnrReportingHandlerTest.kt | 287 ++ .../runtime/crash/build.gradle.kts | 34 + .../runtime/crash/consumer-rules.pro | 0 .../runtime/crash/lint-baseline.xml | 4 + .../runtime/crash/proguard-rules.pro | 1 + .../runtime/crash/src/main/assets/LICENSE.txt | 11 + .../rum/crash/CrashReportingHandler.kt | 188 ++ .../rum/crash/CrashReportingHandlerTest.kt | 196 ++ .../networkrequest/library/build.gradle.kts | 58 + .../library/src/main/assets/LICENSE.txt | 11 + .../rum/library/common/HttpConfigUtil.java | 41 + .../common/HttpInstrumentationConfig.java | 133 + .../HttpUrlInstrumentationConfig.java | 64 + .../HttpUrlReplacements.java | 416 +++ .../tracing/HttpUrlConnectionSingletons.java | 105 + .../tracing/HttpUrlHttpAttributesGetter.java | 83 + .../tracing/RequestPropertySetter.java | 30 + .../library/okhttp3/OkHttp3Singletons.java | 108 + .../ConnectionErrorWrapperInterceptor.java | 45 + .../tracing/OkHttpCallbackAdviceHelper.java | 51 + .../tracing/OkHttpCustomConfigArgument.java | 29 + .../tracing/OkHttpInterceptorUtils.java | 39 + .../tracing/RequestPropertySetter.java | 32 + .../ResendCountContextInterceptor.java | 48 + .../okhttp3/tracing/TracingCallback.java | 49 + .../okhttp3/tracing/TracingInterceptor.java | 79 + .../HttpUrlReplacementsTest.kt | 1307 ++++++++ .../tracing/OkHttpCallbackAdviceHelperTest.kt | 107 + .../okhttp3/tracing/TracingInterceptorTest.kt | 157 + integration-run/build.gradle.kts | 70 + integration-run/src/main/AndroidManifest.xml | 9 + .../src/main/res/drawable/app_icon.xml | 85 + .../src/main/res/values/strings.xml | 4 + .../com/smartlook/intergrationrun/ApiTest.kt | 101 + .../intergrationrun/ApiTestJava.java | 108 + integration/agent/api/build.gradle.kts | 35 + integration/agent/api/consumer-rules.pro | 0 integration/agent/api/lint-baseline.xml | 4 + integration/agent/api/proguard-rules.pro | 1 + .../agent/api/src/main/assets/LICENSE.txt | 11 + .../agent/api/AgentConfiguration.kt | 34 + .../integration/agent/api/CiscoRUMAgent.kt | 67 + .../api/attributes/AttributeConstants.kt | 26 + .../GenericAttributesLogProcessor.kt | 28 + .../api/configuration/ConfigurationManager.kt | 54 + .../agent/api/extension/ConfigurationExt.kt | 29 + .../agent/api/internal/MRUMAgentCore.kt | 100 + .../api/sessionId/SessionIdLogProcessor.kt | 33 + .../api/sessionId/SessionIdSpanProcessor.kt | 36 + .../api/sessionId/SessionStartEventManager.kt | 70 + .../sessionPulse/SessionPulseEventManager.kt | 69 + .../api/state/StateLogRecordProcessor.kt | 29 + .../agent/api/JavaIntegration.java | 51 + integration/agent/internal/build.gradle.kts | 33 + integration/agent/internal/consumer-rules.pro | 0 integration/agent/internal/lint-baseline.xml | 4 + integration/agent/internal/proguard-rules.pro | 1 + .../internal/src/main/assets/LICENSE.txt | 11 + .../agent/internal/AgentIntegration.kt | 134 + .../config/ModuleConfigurationManager.kt | 157 + .../config/RemoteModuleConfiguration.kt | 24 + .../agent/internal/config/ServerClock.kt | 27 + .../extension/ModuleConfigurationsExt.kt | 30 + .../RemoteModuleConfigurationsExt.kt | 65 + .../agent/internal/session/SessionManager.kt | 217 ++ .../agent/internal/state/StateManager.kt | 65 + integration/agent/module/build.gradle.kts | 23 + integration/agent/module/consumer-rules.pro | 0 integration/agent/module/lint-baseline.xml | 4 + integration/agent/module/proguard-rules.pro | 1 + .../agent/module/ModuleConfiguration.kt | 19 + integration/anr/build.gradle.kts | 31 + integration/anr/consumer-rules.pro | 5 + integration/anr/lint-baseline.xml | 4 + integration/anr/proguard-rules.pro | 6 + integration/anr/src/main/AndroidManifest.xml | 13 + integration/anr/src/main/assets/LICENSE.txt | 11 + .../anr/api/ANRModuleConfiguration.kt | 21 + .../anr/configurer/ANRConfigurer.kt | 98 + .../integration/anr/installer/ANRInstaller.kt | 41 + integration/crash/build.gradle.kts | 30 + integration/crash/consumer-rules.pro | 5 + integration/crash/lint-baseline.xml | 4 + integration/crash/proguard-rules.pro | 6 + .../crash/src/main/AndroidManifest.xml | 13 + integration/crash/src/main/assets/LICENSE.txt | 11 + .../crash/api/CrashModuleConfiguration.kt | 21 + .../crash/configurer/CrashConfigurer.kt | 94 + .../crash/installer/CrashInstaller.kt | 41 + integration/networkrequest/build.gradle.kts | 29 + integration/networkrequest/consumer-rules.pro | 5 + integration/networkrequest/lint-baseline.xml | 4 + integration/networkrequest/proguard-rules.pro | 6 + .../src/main/AndroidManifest.xml | 13 + .../src/main/assets/LICENSE.txt | 11 + .../api/NetworkRequestModuleConfiguration.kt | 21 + .../configurer/NetworkRequestConfigurer.kt | 72 + .../installer/NetworkRequestInstaller.kt | 42 + script/clean-up-repo.sh | 8 + script/deobf_tool/DEOBF_README.MD | 38 + script/deobf_tool/MERGE_MAPPINGS_README.MD | 39 + script/deobf_tool/deobf.sh | 58 + script/deobf_tool/merge_mappings.sh | 52 + script/publish-local.sh | 14 + settings.gradle | 34 + 336 files changed, 24082 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .run/DocumentationExtractor.run.xml create mode 100644 .run/Format code.run.xml create mode 100644 .run/ObfuscationDirector.run.xml create mode 100644 .run/Publish snapshot.run.xml create mode 100644 agent/build.gradle.kts create mode 100644 agent/consumer-rules.pro create mode 100644 agent/lint-baseline.xml create mode 100644 agent/proguard-rules.pro create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/docker-compose.yaml create mode 100644 app/keystore.jks create mode 100644 app/otel-collector-config.yaml create mode 100644 app/src/androidTest/README.md create mode 100644 app/src/androidTest/java/com/smartlook/app/lib/TestConstants.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/lib/Util.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/lib/ZipkinCommunicator.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/screens/HttpUrlConnectionScreen.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/screens/MainScreen.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/screens/OkHttpScreen.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/screens/Screen.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/tests/component/BaseTest.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/HttpUrlConnectionTest.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/NetworkRequestsUtil.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/OkHttp3Test.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/tests/e2e/BaseTestE2E.kt create mode 100644 app/src/androidTest/java/com/smartlook/app/tests/e2e/networkrequests/NetworkRequestsTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/smartlook/app/App.kt create mode 100644 app/src/main/java/com/smartlook/app/bridge/TomasBridgeInterface.kt create mode 100644 app/src/main/java/com/smartlook/app/extension/ContextExt.kt create mode 100644 app/src/main/java/com/smartlook/app/extension/FragmentTransactionExt.kt create mode 100644 app/src/main/java/com/smartlook/app/extension/PointFExt.kt create mode 100644 app/src/main/java/com/smartlook/app/extension/ToolbarExt.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/BaseFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/MainActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/SampleFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/adapter/SensitiveItemAdapter.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/adapter/SimpleItemAdapter.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/adapter/SpinnerAdapter.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter2.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/CameraComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/DrawOrderComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/DrawRecompositionComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/ListComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/MeasureRecompositionComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/SurfaceComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/TextFieldComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/VideoComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/ViewDrawOrderComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/WebViewComposeActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/theme/Color.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/compose/theme/Theme.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/dialog/AlertDialog.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/dialog/BottomSheetDialogFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/dialog/DialogActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/dialog/DialogFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerDialog.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerToast.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/httpurlconnection/HttpURLConnectionFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/interaction/FocusActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/menu/MenuFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/okhttp/OkHttpFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/screenshot/AnimationFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotRegionsFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotViewsFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/BridgeInterfaceFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/CollapsingLayoutFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/EmptyActivity.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/ListFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/WebViewFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/WireframeViewsFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyGoogleMapFragment.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyTextView.kt create mode 100644 app/src/main/java/com/smartlook/app/ui/wireframe/model/SimpleItem.kt create mode 100644 app/src/main/java/com/smartlook/app/util/Common.kt create mode 100644 app/src/main/java/com/smartlook/app/util/FragmentAnimation.kt create mode 100644 app/src/main/java/com/smartlook/app/view/BarChartView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/BrokenSeekBar.kt create mode 100644 app/src/main/java/com/smartlook/app/view/CanvasElementsView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/CanvasTextureView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/ColoredLinearLayout.kt create mode 100644 app/src/main/java/com/smartlook/app/view/HardwareBitmapView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/RuntimeShaderView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/SequenceView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/ShadowView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/SimpleCanvasView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/SimpleGLSurfaceView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/TextRunDrawView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/bridge/TomasElement.kt create mode 100644 app/src/main/java/com/smartlook/app/view/bridge/TomasTextureView.kt create mode 100644 app/src/main/java/com/smartlook/app/view/bridge/TomasView.kt create mode 100644 app/src/main/res/anim/fade_in.xml create mode 100644 app/src/main/res/anim/fade_out.xml create mode 100644 app/src/main/res/anim/slow_enter_from_left.xml create mode 100644 app/src/main/res/anim/slow_enter_from_right.xml create mode 100644 app/src/main/res/anim/slow_exit_to_left.xml create mode 100644 app/src/main/res/anim/slow_exit_to_right.xml create mode 100644 app/src/main/res/animator/slow_fade_in.xml create mode 100644 app/src/main/res/animator/slow_fade_out.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/app_icon.xml create mode 100644 app/src/main/res/drawable/bcg_gradient_blue.xml create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/drawable/ic_back.xml create mode 100644 app/src/main/res/drawable/ic_bitmap_big.png create mode 100644 app/src/main/res/drawable/ic_bitmap_sample_1.jpg create mode 100644 app/src/main/res/drawable/ic_bitmap_sample_2.jpg create mode 100644 app/src/main/res/drawable/ic_bitmap_sample_3.jpg create mode 100644 app/src/main/res/drawable/ic_bitmap_sample_4.jpg create mode 100644 app/src/main/res/drawable/ic_bitmap_sample_5.jpg create mode 100644 app/src/main/res/drawable/ic_bitmap_sample_6.jpg create mode 100644 app/src/main/res/drawable/ic_bitmap_small.png create mode 100644 app/src/main/res/drawable/ic_crop_test.xml create mode 100644 app/src/main/res/drawable/ic_gradient.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_progress.xml create mode 100644 app/src/main/res/drawable/ic_select_all.xml create mode 100644 app/src/main/res/drawable/ic_tab_dot.xml create mode 100644 app/src/main/res/drawable/ic_test_rect_1.xml create mode 100644 app/src/main/res/drawable/ic_test_rect_2.xml create mode 100644 app/src/main/res/drawable/ic_vector_big.xml create mode 100644 app/src/main/res/drawable/ic_vector_small.xml create mode 100644 app/src/main/res/drawable/sel_button.xml create mode 100644 app/src/main/res/drawable/sel_text.xml create mode 100644 app/src/main/res/layout/activity_empty.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/fragment_bridge_interface.xml create mode 100644 app/src/main/res/layout/fragment_collapsing_layout.xml create mode 100644 app/src/main/res/layout/fragment_dialog.xml create mode 100644 app/src/main/res/layout/fragment_dummy.xml create mode 100644 app/src/main/res/layout/fragment_http_url_connection.xml create mode 100644 app/src/main/res/layout/fragment_menu.xml create mode 100644 app/src/main/res/layout/fragment_okhttp.xml create mode 100644 app/src/main/res/layout/fragment_screenshot_regions.xml create mode 100644 app/src/main/res/layout/fragment_screenshot_views.xml create mode 100644 app/src/main/res/layout/fragment_transactions.xml create mode 100644 app/src/main/res/layout/fragment_web_view.xml create mode 100644 app/src/main/res/layout/fragment_wireframe_list.xml create mode 100644 app/src/main/res/layout/fragment_wireframe_views.xml create mode 100644 app/src/main/res/layout/item_example.xml create mode 100644 app/src/main/res/layout/item_sensitive.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/raw/rainy_day.mp4 create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/Configurations.kt create mode 100644 buildSrc/src/main/kotlin/Dependencies.kt create mode 100644 buildSrc/src/main/kotlin/Ktlint.kt create mode 100644 buildSrc/src/main/kotlin/TaskGroups.kt create mode 100644 buildSrc/src/main/kotlin/plugins/ConfigAndroidApp.kt create mode 100644 buildSrc/src/main/kotlin/plugins/ConfigAndroidLibrary.kt create mode 100644 buildSrc/src/main/kotlin/plugins/ConfigKtLint.kt create mode 100644 buildSrc/src/main/kotlin/plugins/ConfigLint.kt create mode 100644 buildSrc/src/main/kotlin/plugins/ConfigPublish.kt create mode 100644 buildSrc/src/main/kotlin/plugins/LocalPlugin.kt create mode 100644 buildSrc/src/main/kotlin/utils/Extensions.kt create mode 100644 buildSrc/src/main/kotlin/utils/ObfuscationTypeAnalyzer.kt create mode 100644 buildSrc/src/main/kotlin/utils/ProjectProperties.kt create mode 100644 common/otel/api/build.gradle.kts create mode 100644 common/otel/api/consumer-rules.pro create mode 100644 common/otel/api/lint-baseline.xml create mode 100644 common/otel/api/proguard-rules.pro create mode 100644 common/otel/api/src/main/AndroidManifest.xml create mode 100644 common/otel/api/src/main/assets/LICENSE.txt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/OpenTelemetry.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/OpenTelemetryInitializer.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/internal/OfflineOtelDataProcessor.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/internal/Resources.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/logRecord/AndroidLogRecordExporter.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/logRecord/UploadOtelLogRecordData.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/logRecord/UploadOtelLogRecordDataJob.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/span/AndroidSpanExporter.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/span/UploadOtelSpanData.kt create mode 100644 common/otel/api/src/main/java/com/cisco/mrum/common/otel/api/span/UploadOtelSpanDataJob.kt create mode 100644 common/otel/internal/build.gradle.kts create mode 100644 common/otel/internal/consumer-rules.pro create mode 100644 common/otel/internal/lint-baseline.xml create mode 100644 common/otel/internal/proguard-rules.pro create mode 100644 common/otel/internal/src/main/assets/LICENSE.txt create mode 100644 common/otel/internal/src/main/java/com/cisco/mrum/common/otel/internal/storage/OtelStorage.kt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 instrumentation/buildtime/build-info-injector/build.gradle.kts create mode 100644 instrumentation/buildtime/build-info-injector/src/main/assets/LICENSE.txt create mode 100644 instrumentation/buildtime/build-info-injector/src/main/java/com/cisco/android/rum/buildInfoInjector/BuildInfoInjectorPlugin.kt create mode 100644 instrumentation/buildtime/build-info-injector/src/main/java/com/cisco/android/rum/buildInfoInjector/utils/MappingFileModifier.kt create mode 100644 instrumentation/buildtime/build-info-injector/src/main/java/com/cisco/android/rum/buildInfoInjector/utils/ObfuscationTypeAnalyzer.kt create mode 100644 instrumentation/buildtime/networkrequest/bci/build.gradle.kts create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/assets/LICENSE.txt create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/java/com/cisco/android/rum/bci/httpurlconnection/HttpUrlConnectionPlugin.java create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/java/com/cisco/android/rum/bci/okhttp3/OkHttpCallbackAdvice.java create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/java/com/cisco/android/rum/bci/okhttp3/OkHttpCallbackPlugin.java create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/java/com/cisco/android/rum/bci/okhttp3/OkHttpClientAdvice.java create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/java/com/cisco/android/rum/bci/okhttp3/OkHttpClientPlugin.java create mode 100644 instrumentation/buildtime/networkrequest/bci/src/main/resources/META-INF/net.bytebuddy/build.plugins create mode 100644 instrumentation/buildtime/plugin/build.gradle.kts create mode 100644 instrumentation/buildtime/plugin/src/main/assets/LICENSE.txt create mode 100644 instrumentation/buildtime/plugin/src/main/java/com/cisco/android/rum/plugin/AndroidInstrumentationPlugin.java create mode 100644 instrumentation/runtime/anr/build.gradle.kts create mode 100644 instrumentation/runtime/anr/consumer-rules.pro create mode 100644 instrumentation/runtime/anr/lint-baseline.xml create mode 100644 instrumentation/runtime/anr/proguard-rules.pro create mode 100644 instrumentation/runtime/anr/src/main/assets/LICENSE.txt create mode 100644 instrumentation/runtime/anr/src/main/java/com/cisco/android/rum/anr/ANRWatchDog.kt create mode 100644 instrumentation/runtime/anr/src/main/java/com/cisco/android/rum/anr/AnrReportingHandler.kt create mode 100644 instrumentation/runtime/anr/src/test/java/com/cisco/android/rum/anr/ANRWatchDogTest.kt create mode 100644 instrumentation/runtime/anr/src/test/java/com/cisco/android/rum/anr/AnrReportingHandlerTest.kt create mode 100644 instrumentation/runtime/crash/build.gradle.kts create mode 100644 instrumentation/runtime/crash/consumer-rules.pro create mode 100644 instrumentation/runtime/crash/lint-baseline.xml create mode 100644 instrumentation/runtime/crash/proguard-rules.pro create mode 100644 instrumentation/runtime/crash/src/main/assets/LICENSE.txt create mode 100644 instrumentation/runtime/crash/src/main/java/com/cisco/android/rum/crash/CrashReportingHandler.kt create mode 100644 instrumentation/runtime/crash/src/test/java/com/cisco/android/rum/crash/CrashReportingHandlerTest.kt create mode 100644 instrumentation/runtime/networkrequest/library/build.gradle.kts create mode 100644 instrumentation/runtime/networkrequest/library/src/main/assets/LICENSE.txt create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/common/HttpConfigUtil.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/common/HttpInstrumentationConfig.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/httpurlconnection/HttpUrlInstrumentationConfig.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/httpurlconnection/HttpUrlReplacements.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/httpurlconnection/tracing/HttpUrlConnectionSingletons.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/httpurlconnection/tracing/HttpUrlHttpAttributesGetter.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/httpurlconnection/tracing/RequestPropertySetter.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/OkHttp3Singletons.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/ConnectionErrorWrapperInterceptor.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/OkHttpCallbackAdviceHelper.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/OkHttpCustomConfigArgument.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/OkHttpInterceptorUtils.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/RequestPropertySetter.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/ResendCountContextInterceptor.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/TracingCallback.java create mode 100644 instrumentation/runtime/networkrequest/library/src/main/java/com/cisco/android/rum/library/okhttp3/tracing/TracingInterceptor.java create mode 100644 instrumentation/runtime/networkrequest/library/src/test/java/com/cisco/android/rum/library/httpurlconnection/HttpUrlReplacementsTest.kt create mode 100644 instrumentation/runtime/networkrequest/library/src/test/java/com/cisco/android/rum/library/okhttp3/tracing/OkHttpCallbackAdviceHelperTest.kt create mode 100644 instrumentation/runtime/networkrequest/library/src/test/java/com/cisco/android/rum/library/okhttp3/tracing/TracingInterceptorTest.kt create mode 100644 integration-run/build.gradle.kts create mode 100644 integration-run/src/main/AndroidManifest.xml create mode 100644 integration-run/src/main/res/drawable/app_icon.xml create mode 100644 integration-run/src/main/res/values/strings.xml create mode 100644 integration-run/src/test/java/com/smartlook/intergrationrun/ApiTest.kt create mode 100644 integration-run/src/test/java/com/smartlook/intergrationrun/ApiTestJava.java create mode 100644 integration/agent/api/build.gradle.kts create mode 100644 integration/agent/api/consumer-rules.pro create mode 100644 integration/agent/api/lint-baseline.xml create mode 100644 integration/agent/api/proguard-rules.pro create mode 100644 integration/agent/api/src/main/assets/LICENSE.txt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/AgentConfiguration.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/CiscoRUMAgent.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/attributes/AttributeConstants.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/attributes/GenericAttributesLogProcessor.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/configuration/ConfigurationManager.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/extension/ConfigurationExt.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/internal/MRUMAgentCore.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/sessionId/SessionIdLogProcessor.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/sessionId/SessionIdSpanProcessor.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/sessionId/SessionStartEventManager.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/sessionPulse/SessionPulseEventManager.kt create mode 100644 integration/agent/api/src/main/java/com/cisco/android/rum/integration/agent/api/state/StateLogRecordProcessor.kt create mode 100644 integration/agent/api/src/test/java/com/cisco/android/rum/integration/agent/api/JavaIntegration.java create mode 100644 integration/agent/internal/build.gradle.kts create mode 100644 integration/agent/internal/consumer-rules.pro create mode 100644 integration/agent/internal/lint-baseline.xml create mode 100644 integration/agent/internal/proguard-rules.pro create mode 100644 integration/agent/internal/src/main/assets/LICENSE.txt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/AgentIntegration.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/config/ModuleConfigurationManager.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/config/RemoteModuleConfiguration.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/config/ServerClock.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/extension/ModuleConfigurationsExt.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/extension/RemoteModuleConfigurationsExt.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/session/SessionManager.kt create mode 100644 integration/agent/internal/src/main/java/com/cisco/android/rum/integration/agent/internal/state/StateManager.kt create mode 100644 integration/agent/module/build.gradle.kts create mode 100644 integration/agent/module/consumer-rules.pro create mode 100644 integration/agent/module/lint-baseline.xml create mode 100644 integration/agent/module/proguard-rules.pro create mode 100644 integration/agent/module/src/main/java/com/cisco/android/rum/integration/agent/module/ModuleConfiguration.kt create mode 100644 integration/anr/build.gradle.kts create mode 100644 integration/anr/consumer-rules.pro create mode 100644 integration/anr/lint-baseline.xml create mode 100644 integration/anr/proguard-rules.pro create mode 100644 integration/anr/src/main/AndroidManifest.xml create mode 100644 integration/anr/src/main/assets/LICENSE.txt create mode 100644 integration/anr/src/main/java/com/cisco/android/rum/integration/anr/api/ANRModuleConfiguration.kt create mode 100644 integration/anr/src/main/java/com/cisco/android/rum/integration/anr/configurer/ANRConfigurer.kt create mode 100644 integration/anr/src/main/java/com/cisco/android/rum/integration/anr/installer/ANRInstaller.kt create mode 100644 integration/crash/build.gradle.kts create mode 100644 integration/crash/consumer-rules.pro create mode 100644 integration/crash/lint-baseline.xml create mode 100644 integration/crash/proguard-rules.pro create mode 100644 integration/crash/src/main/AndroidManifest.xml create mode 100644 integration/crash/src/main/assets/LICENSE.txt create mode 100644 integration/crash/src/main/java/com/cisco/android/rum/integration/crash/api/CrashModuleConfiguration.kt create mode 100644 integration/crash/src/main/java/com/cisco/android/rum/integration/crash/configurer/CrashConfigurer.kt create mode 100644 integration/crash/src/main/java/com/cisco/android/rum/integration/crash/installer/CrashInstaller.kt create mode 100644 integration/networkrequest/build.gradle.kts create mode 100644 integration/networkrequest/consumer-rules.pro create mode 100644 integration/networkrequest/lint-baseline.xml create mode 100644 integration/networkrequest/proguard-rules.pro create mode 100644 integration/networkrequest/src/main/AndroidManifest.xml create mode 100644 integration/networkrequest/src/main/assets/LICENSE.txt create mode 100644 integration/networkrequest/src/main/java/com/cisco/android/rum/integration/networkrequest/api/NetworkRequestModuleConfiguration.kt create mode 100644 integration/networkrequest/src/main/java/com/cisco/android/rum/integration/networkrequest/configurer/NetworkRequestConfigurer.kt create mode 100644 integration/networkrequest/src/main/java/com/cisco/android/rum/integration/networkrequest/installer/NetworkRequestInstaller.kt create mode 100755 script/clean-up-repo.sh create mode 100644 script/deobf_tool/DEOBF_README.MD create mode 100644 script/deobf_tool/MERGE_MAPPINGS_README.MD create mode 100644 script/deobf_tool/deobf.sh create mode 100644 script/deobf_tool/merge_mappings.sh create mode 100755 script/publish-local.sh create mode 100644 settings.gradle diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..9ff1a15ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# In order to know which rules to disable for auto formatting just run KtLint without -F (formatting) + +[*.{kt,kts}] +ktlint_code_style = android_studio +ktlint_standard_max-line-length = disabled +ktlint_standard_parameter-list-wrapping = disabled +ktlint_standard_function-signature = disabled +ktlint_standard_argument-list-wrapping = disabled +ktlint_standard_multiline-if-else = disabled +ktlint_standard_wrapping = disabled +ktlint_standard_no-empty-first-line-in-method-block = disabled +ktlint_standard_discouraged-comment-location = disabled +ktlint_standard_property-wrapping = disabled +ktlint_standard_enum-wrapping = disabled +ktlint_standard_statement-wrapping = disabled +ktlint_standard_comment-wrapping = disabled +ktlint_standard_property-naming = disabled +ktlint_standard_import-ordering = disabled +ktlint_standard_paren-spacing = disabled +ktlint_standard_indent = disabled +ktlint_standard_comment-spacing = disabled +ktlint_standard_kdoc = disabled +ktlint_standard_blank-line-between-when-conditions = disabled +# Jetpack Compose functions +ktlint_standard_function-naming = disabled + +# Necessary for 'ktlint_standard_wrapping' +ktlint_standard_trailing-comma-on-call-site = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..fd251755f --- /dev/null +++ b/.gitignore @@ -0,0 +1,117 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# api validation - checked out folders should be ignored +!apiValidator/ +apiValidator/* + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +*.iml + +# Not needed for now +.idea/* +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# out map +smartlooksdk/out.map + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +.idea/modules.xml +.idea/*.iml +.idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Sonarlint plugin +.idea/sonarlint + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Maven local repo +**/repo/* diff --git a/.run/DocumentationExtractor.run.xml b/.run/DocumentationExtractor.run.xml new file mode 100644 index 000000000..8286e73b3 --- /dev/null +++ b/.run/DocumentationExtractor.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/Format code.run.xml b/.run/Format code.run.xml new file mode 100644 index 000000000..c08d2ca28 --- /dev/null +++ b/.run/Format code.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.run/ObfuscationDirector.run.xml b/.run/ObfuscationDirector.run.xml new file mode 100644 index 000000000..71de4388e --- /dev/null +++ b/.run/ObfuscationDirector.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/Publish snapshot.run.xml b/.run/Publish snapshot.run.xml new file mode 100644 index 000000000..273b819e5 --- /dev/null +++ b/.run/Publish snapshot.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/agent/build.gradle.kts b/agent/build.gradle.kts new file mode 100644 index 000000000..634ff667e --- /dev/null +++ b/agent/build.gradle.kts @@ -0,0 +1,29 @@ +import plugins.ConfigAndroidLibrary +import utils.artifactIdProperty +import utils.versionProperty + +plugins { + id("com.android.library") + id("kotlin-android") +} + +apply() +apply() + +ext { + set(artifactIdProperty, "rum-agent") + set(versionProperty, Configurations.sdkVersionName) +} + +android { + namespace = "com.cisco.android.rum" +} + +dependencies { + api(project(":integration:agent:api")) + // TODO api(project(":integration:session-recording")) + api(project(":integration:crash")) + api(project(":integration:anr")) + api(project(":integration:networkrequest")) +} + diff --git a/agent/consumer-rules.pro b/agent/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/agent/lint-baseline.xml b/agent/lint-baseline.xml new file mode 100644 index 000000000..27ab162a6 --- /dev/null +++ b/agent/lint-baseline.xml @@ -0,0 +1,4 @@ + + + + diff --git a/agent/proguard-rules.pro b/agent/proguard-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 000000000..0868fbdbb --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,171 @@ +import Dependencies.kotlinStdlibJdk8 +import plugins.ConfigAndroidApp +import java.net.InetAddress + + +plugins { + id("com.android.application") + id("kotlin-android") + //TODO: Causes issues while building with common:otel module in minifyR8 task. + // Need to figure out a way to depend on plugin project instead of published jar + // Uncomment this to test HttpURLConnection and build just the app to test + //id("com.cisco.android.rum-plugin") version "24.4.10-2246" +} + +apply() + +android { + namespace = "com.smartlook.app" + + defaultConfig { + applicationId = "com.smartlook.app" + versionCode = Configurations.sdkVersionCode + versionName = Configurations.sdkVersionName + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments.put("clearPackageData", "true") + } + + signingConfigs { + create("release") { + storeFile = project.file("keystore.jks") + storePassword = "${project.findProperty("splunk_test_app_store_password")}" + keyAlias = "${project.findProperty("splunk_test_app_key_alias")}" + keyPassword = "${project.findProperty("splunk_test_app_key_password")}" + } + } + + buildTypes { + getByName("debug") { + resValue("bool", "leak_canary_add_launcher_icon", "false") + signingConfig = signingConfigs.getByName("release") + val ip = InetAddress.getLocalHost().hostAddress + buildConfigField("String", "IP_ADDRESS", "\"$ip\"") + } + getByName("release") { + isMinifyEnabled = true + signingConfig = signingConfigs.getByName("release") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + testOptions { + unitTests.apply { + isIncludeAndroidResources = true + } + execution = "ANDROIDX_TEST_ORCHESTRATOR" + } + + buildFeatures { + viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = Dependencies.Android.Compose.compilerVersion + } + + packagingOptions { + resources { + excludes += "META-INF/AL2.0" + excludes += "META-INF/LGPL2.1" + } + } + + lint { + abortOnError = false + } +} + +dependencies { + implementation(kotlinStdlibJdk8) + + //implementation("com.cisco.android:rum-agent:24.4.10-2246") + // TODO: this is here just so we do not have duplicate logic, it is not publicly available + //implementation("com.cisco.android:rum-common-utils:24.4.10-2246") + + // TODO implementation(project(":common:utils")) + implementation(project(":agent")) + //TODO: Below dependency can be removed once we uncomment the plugin id. + implementation(project(":instrumentation:runtime:networkrequest:library")) + + implementation(Dependencies.Android.appcompat) + implementation(Dependencies.Android.constraintLayout) + implementation(Dependencies.Android.activityKtx) + implementation(Dependencies.Android.fragmentKtx) + + /** + * Play services basement must be explicitly included since a newer version is being enforced than what is transitively used by play services maps. + */ + implementation(Dependencies.Android.playServicesMap) + implementation(Dependencies.Android.playServicesBasement) + + implementation(Dependencies.Android.cardView) + implementation(Dependencies.Android.material) + + implementation(Dependencies.Android.Compose.activity) + implementation(Dependencies.Android.Compose.ui) + implementation(Dependencies.Android.Compose.material) + implementation(Dependencies.Android.Compose.animation) + implementation(Dependencies.Android.Compose.materialIconsExtended) + implementation(Dependencies.Android.Compose.toolingPreview) + + /** + * Okio must be explicitly included since a newer version is being enforced than what is transitively used by OkHttp. + */ + implementation(Dependencies.okhttp) + implementation(Dependencies.okio) + + debugImplementation(Dependencies.Android.Compose.uiTooling) + debugImplementation(Dependencies.AndroidDebug.leakCanary) + + /** + * Explicit version of guava jre must be forced because ext truth uses one with vulnerabilities. + */ + androidTestImplementation(Dependencies.guavaAndroid) + androidTestImplementation(Dependencies.AndroidTest.testExtTruth) + + androidTestImplementation(Dependencies.AndroidTest.junit) + androidTestImplementation(Dependencies.AndroidTest.mockk) + androidTestImplementation(Dependencies.AndroidTest.serialization) + androidTestImplementation(Dependencies.AndroidTest.testRules) + androidTestImplementation(Dependencies.AndroidTest.testRunner) + androidTestImplementation(Dependencies.AndroidTest.uiAutomator) + + androidTestImplementation(Dependencies.AndroidTest.Compose.junit) + + /** + * Jsoup must be explicitly included since a newer version is being enforced than what is transitively used by espresso contrib. + */ + androidTestImplementation(Dependencies.AndroidTest.Espresso.contrib) + androidTestImplementation(Dependencies.AndroidTest.Espresso.jsoup) + + androidTestImplementation(Dependencies.AndroidTest.Espresso.core) + androidTestImplementation(Dependencies.AndroidTest.Espresso.idlingConcurrent) + androidTestImplementation(Dependencies.AndroidTest.Espresso.idlingResource) + androidTestImplementation(Dependencies.AndroidTest.Espresso.intents) + androidTestImplementation(Dependencies.AndroidTest.Espresso.web) + + androidTestImplementation(Dependencies.okhttp) + androidTestImplementation(Dependencies.AndroidTest.okhttpLogging) + + androidTestImplementation(Dependencies.Test.jsonassert) + + androidTestUtil(Dependencies.AndroidTest.testOrchestrator) + + implementation(Dependencies.Android.cameraLifecycle) + implementation(Dependencies.Android.cameraExtensions) + implementation(Dependencies.Android.cameraView) + + /** + * Explicit version of guava jre must be forced because ext truth uses one with vulnerabilities. + */ + implementation(Dependencies.guavaAndroid) + implementation(Dependencies.Android.exoPlayer) +} + +tasks.register("startOtelCollectorForTests") { + group = "docker" + description = "Start services defined in docker-compose.yaml" + commandLine("docker-compose", "-f", "docker-compose.yaml", "up", "-d") +} diff --git a/app/docker-compose.yaml b/app/docker-compose.yaml new file mode 100644 index 000000000..f4f3f77b8 --- /dev/null +++ b/app/docker-compose.yaml @@ -0,0 +1,24 @@ + +services: + + zipkin: + container_name: zipkin + image: openzipkin/zipkin:latest + ports: + - "9411:9411" + + otel-collector: + image: otel/opentelemetry-collector:latest + command: [--config=/etc/otel-collector-config.yaml] + volumes: + - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml + ports: + - 1888:1888 # pprof extension + - 8888:8888 # Prometheus metrics exposed by the collector + - 8889:8889 # Prometheus exporter metrics + - 13133:13133 # health_check extension + - 4317:4317 # OTLP gRPC receiver + - 4318:4318 # OTLP http receiver + - 55679:55679 # zpages extension + depends_on: + - zipkin \ No newline at end of file diff --git a/app/keystore.jks b/app/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..bec9ac8270fb52307f30fb0b99f3d9117bb355c5 GIT binary patch literal 2559 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 I42&-P7wlEDs{jB1 literal 0 HcmV?d00001 diff --git a/app/otel-collector-config.yaml b/app/otel-collector-config.yaml new file mode 100644 index 000000000..eccd160fb --- /dev/null +++ b/app/otel-collector-config.yaml @@ -0,0 +1,51 @@ +processors: + batch: + resource: + attributes: + - key: service.namespace + action: upsert + value: "smartlook-android" + - key: service.name + action: upsert + value: "android-service" + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + cors: + allowed_origins: + - http://* + - https://* + endpoint: 0.0.0.0:4318 + traces_url_path: "/eum/v1/traces" + logs_url_path: "/eum/v1/logs" + +exporters: + file: + path: ./exports.json + debug: + verbosity: detailed + zipkin: + endpoint: "http://zipkin:9411/api/v2/spans" + +extensions: + health_check: + pprof: + endpoint: :1888 + zpages: + endpoint: :55679 + +service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [otlp] + processors: [resource] + exporters: [zipkin, debug] + logs: + receivers: [otlp] + processors: [batch] + exporters: [debug] \ No newline at end of file diff --git a/app/src/androidTest/README.md b/app/src/androidTest/README.md new file mode 100644 index 000000000..0480a8952 --- /dev/null +++ b/app/src/androidTest/README.md @@ -0,0 +1,19 @@ +# Running Component Test +This is a work in progress +First, start the Otel Collector by running: ```./gradlew startOtelCollectorForTests``` + +Then, you'll want to update the Application under test to use the otel collector IP. Make sure +you don't point at localhost because it will try to use the device localhost, not the machines. +A build config field has been added for this, ```BuildConfig.IP_ADDRESS```. Update collector URL. + +This will be an automated step eventually. + +Then start running the test you want. + +The Setup only includes Otel collector and Zipkin for now. + +# End-to-End Flow +The End-to-End flow is just a flow to generate data for end-to-end tests. There will be a separate +Playwright based project [here](https://bitbucket.corp.appdynamics.com/projects/MRUM_AC/repos/mrum-e2e-tests/browse) to verify the DashUI view relating to the test data. + +More on that later. diff --git a/app/src/androidTest/java/com/smartlook/app/lib/TestConstants.kt b/app/src/androidTest/java/com/smartlook/app/lib/TestConstants.kt new file mode 100644 index 000000000..2a1b01f18 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/lib/TestConstants.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.lib + +import com.smartlook.app.BuildConfig + +object TestConstants { + const val DEFAULT_NETWORK_TIMEOUT = 120_000L + const val DEFAULT_MELT_TIMEOUT = 20_000L + const val ZIPKIN_URL = BuildConfig.IP_ADDRESS +} diff --git a/app/src/androidTest/java/com/smartlook/app/lib/Util.kt b/app/src/androidTest/java/com/smartlook/app/lib/Util.kt new file mode 100644 index 000000000..26aded199 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/lib/Util.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.lib + +import android.app.Instrumentation +import android.content.Context +import androidx.test.platform.app.InstrumentationRegistry + +/** + * Instrumentation + */ +val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + +/** + * The target Context + */ +val targetContext: Context = instrumentation.targetContext + +/** + * A resource type enum to specify what kind of resource you want to interact with. + */ +enum class ResourceType { + RESOURCE_ID, RESOURCE_STRING +} + +/** + * This will take an Int and return a resource type or throw an error if the resource type doesn't + * match. + */ +val Int.resType: ResourceType + get() = when (targetContext.resources.getResourceTypeName(this)) { + "id" -> ResourceType.RESOURCE_ID + "string" -> ResourceType.RESOURCE_STRING + else -> throw NoSuchElementException() + } diff --git a/app/src/androidTest/java/com/smartlook/app/lib/ZipkinCommunicator.kt b/app/src/androidTest/java/com/smartlook/app/lib/ZipkinCommunicator.kt new file mode 100644 index 000000000..aefa14306 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/lib/ZipkinCommunicator.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.lib + +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.logging.HttpLoggingInterceptor +import java.net.URL + +object ZipkinCommunicator { + private val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) + private val client = OkHttpClient.Builder() + .addInterceptor(interceptor) + .build() + + private const val ZIPKIN_URL = "http://${TestConstants.ZIPKIN_URL}:9411/api/v2/traces" + private val request = Request.Builder() + .url(ZIPKIN_URL) + .build() + + fun getAllTraces(): String { + var responseString: String = "" + + runCatching { + client.newCall(request).execute() + }.onFailure { error -> + throw Exception("Unable to communicate with Zipkin. Error: $error") + }.onSuccess { response -> + responseString = response.body?.string() ?: "" + } + + return responseString + } + + fun getAllTracesBySpanName(spanName: String, duration: Long): String { + var responseString: String = "" + // TODO - Add lookback into the request builder once spans are working again. + runCatching { + request.newBuilder().url(URL("${TestConstants.ZIPKIN_URL}/?spanName=$spanName")) + client.newCall(request).execute() + }.onFailure { error -> + throw Exception("Unable to communicate with Zipkin. Error: $error") + }.onSuccess { response -> + responseString = response.body?.string() ?: "" + } + + return responseString + } + + /** + * There isn't a great way to clear the Zipkin database, so we can limit our results using + * Zipkin's Look-back option. Here we use a test start time and calculate an end time and look + * back for that duration only. + */ + fun getTestDuration(startTime: Long): Long = System.currentTimeMillis() - startTime + 1000 +} diff --git a/app/src/androidTest/java/com/smartlook/app/screens/HttpUrlConnectionScreen.kt b/app/src/androidTest/java/com/smartlook/app/screens/HttpUrlConnectionScreen.kt new file mode 100644 index 000000000..3a2e4277e --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/screens/HttpUrlConnectionScreen.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.screens + +import android.view.View +import com.smartlook.app.R +import org.hamcrest.Matcher + +class HttpUrlConnectionScreen : Screen() { + + /** + * EditTexts + */ + val customUrlInputEditText: Matcher = view(R.id.customUrl) + + /** + * Buttons + */ + val customUrlGetButton: Matcher = view(R.id.customUrlGet) + val successfulGetButton: Matcher = view(R.id.successfulGet) + val unsuccessfulGetButton: Matcher = view(R.id.unSuccessfulGet) + val getWithoutInputStreamButton: Matcher = view(R.id.getWithoutInputStream) + val fourConcurrentGetRequestsButton: Matcher = view(R.id.fourConcurrentGetRequests) + val postButton: Matcher = view(R.id.post) + val sustainedConnectionButton: Matcher = view(R.id.sustainedConnection) + val stalledRequestButton: Matcher = view(R.id.stalledRequest) + + fun getCustomUrl(url: String) { + screen { + customUrlInputEditText.replaceText(url) + customUrlGetButton.click() + } + } +} diff --git a/app/src/androidTest/java/com/smartlook/app/screens/MainScreen.kt b/app/src/androidTest/java/com/smartlook/app/screens/MainScreen.kt new file mode 100644 index 000000000..f361afc49 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/screens/MainScreen.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.screens + +import android.view.View +import com.smartlook.app.R +import org.hamcrest.Matcher + +class MainScreen : Screen() { + + /** + * Buttons + */ + val okHttpSampleCallsButton: Matcher = view(R.id.okhttp_sample_calls) + val httpUrlConnectionButton: Matcher = view(R.id.httpurlconnection) +} diff --git a/app/src/androidTest/java/com/smartlook/app/screens/OkHttpScreen.kt b/app/src/androidTest/java/com/smartlook/app/screens/OkHttpScreen.kt new file mode 100644 index 000000000..5fd94d795 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/screens/OkHttpScreen.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.screens + +import android.view.View +import com.smartlook.app.R +import org.hamcrest.Matcher + +class OkHttpScreen : Screen() { + + /** + * Buttons + */ + val synchronousGetButton: Matcher = view(R.id.synchronousGet) + val asynchronousGetButton: Matcher = view(R.id.asynchronousGet) + val multipleHeadersButton: Matcher = view(R.id.multipleHeaders) + val postMarkdownButton: Matcher = view(R.id.postMarkdown) + val postStreamingButton: Matcher = view(R.id.postStreaming) + val postFileButton: Matcher = view(R.id.postFile) + val postFromParametersButton: Matcher = view(R.id.postFormParameters) + val postMultipartRequestButton: Matcher = view(R.id.postMutlipartRequest) + val responseCachingButton: Matcher = view(R.id.responseCaching) + val canceledCallButton: Matcher = view(R.id.canceledCall) +} diff --git a/app/src/androidTest/java/com/smartlook/app/screens/Screen.kt b/app/src/androidTest/java/com/smartlook/app/screens/Screen.kt new file mode 100644 index 000000000..df2406b16 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/screens/Screen.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.screens + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException +import androidx.test.espresso.PerformException +import androidx.test.espresso.ViewAssertion +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.smartlook.app.lib.ResourceType +import com.smartlook.app.lib.TestConstants +import com.smartlook.app.lib.resType +import org.hamcrest.Matcher +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Utility function to call a specific screen. Instead of instantiating page objects, + * simply call the screen { ... }. + * + * Example: screen { myButton.click() } + */ +inline fun screen(init: T.() -> Unit): T? = + T::class.java.getDeclaredConstructor().newInstance().apply { init() } + +open class Screen { + + /** + * This utility function simplifies the need to call a specific resource. + * onView(withId(...)) or onView(withText(...)) simply becomes view(...). + */ + fun view(element: T): Matcher = when (element) { + is Int -> when (element.resType) { + ResourceType.RESOURCE_ID -> withId(element) + ResourceType.RESOURCE_STRING -> withText(element) + } + + is String -> withText(element) + else -> throw NoSuchElementException() + } + + /** + * This safe click function will attempt to scroll to the view prior to clicking. + * If the view is already visible, it will bypass the exception and click the item. + */ + fun Matcher.click(): ViewInteraction { + scrollFirst(this) + return onView(this).perform(ViewActions.click()) + } + + fun Matcher.replaceText(text: String): ViewInteraction { + scrollFirst(this) + return onView(this).perform(ViewActions.replaceText(text)) + } + + /** + * A helper function to silently handle scrolling errors which are thrown on some elements that + * cannot be scrolled on, for example toolbars. + */ + private fun scrollFirst(matcher: Matcher) { + try { + onView(matcher).perform(ViewActions.scrollTo()) + } catch (exception: PerformException) { + // Do nothing, view already visible or cannot be scrolled on + } + } + + /** Default back option. */ + fun goBack() = Espresso.pressBack() + + /** + * Waits for a [Matcher]. + */ + fun waitForView( + matcher: Matcher, + timeout: Long = TestConstants.DEFAULT_NETWORK_TIMEOUT, + timeUnit: TimeUnit = TimeUnit.MILLISECONDS + ): Boolean { + val startTime = System.currentTimeMillis() + val endTime = startTime + timeUnit.toMillis(timeout) + val latch = CountDownLatch(1) + + val viewAssertion = ViewAssertion { view, noViewFoundException -> + if (noViewFoundException != null) { + throw noViewFoundException + } + + if (ViewMatchers.isDisplayed().matches(view)) { + latch.countDown() + } + } + + do { + try { + onView(matcher).check(viewAssertion) + + if (latch.await(50, TimeUnit.MILLISECONDS)) { + return true // Found the view and it's visible + } + } catch (e: NoMatchingViewException) { + // View not found, sleep for a short duration before retrying + Thread.sleep(50) + } + + if (System.currentTimeMillis() > endTime) { + throw AssertionError("View with matcher $matcher not found within the timeout") + } + } while (true) + } + + /** Selects an item from a RecyclerView at a position */ + fun selectItemFromRecyclerView(recyclerView: Matcher, position: Int) { + onView(recyclerView) + .perform( + RecyclerViewActions.actionOnItemAtPosition( + position, + ViewActions.click() + ) + ) + } + + /** Wait for some time to allow any MELT data to be sent. */ + fun waitForSendingData( + timeout: Long = TestConstants.DEFAULT_MELT_TIMEOUT + ) { + Thread.sleep(timeout) + } +} diff --git a/app/src/androidTest/java/com/smartlook/app/tests/component/BaseTest.kt b/app/src/androidTest/java/com/smartlook/app/tests/component/BaseTest.kt new file mode 100644 index 000000000..0b32f9360 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/tests/component/BaseTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.tests.component + +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.smartlook.app.ui.MainActivity +import org.junit.Rule + +abstract class BaseTest { + + @get:Rule + val scenario = ActivityScenarioRule(MainActivity::class.java) +} diff --git a/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/HttpUrlConnectionTest.kt b/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/HttpUrlConnectionTest.kt new file mode 100644 index 000000000..114f32689 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/HttpUrlConnectionTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.tests.component.networkrequests + +import com.smartlook.app.lib.ZipkinCommunicator +import com.smartlook.app.screens.HttpUrlConnectionScreen +import com.smartlook.app.screens.MainScreen +import com.smartlook.app.screens.screen +import com.smartlook.app.tests.component.BaseTest +import org.junit.Before +import org.junit.Test + +class HttpUrlConnectionTest : BaseTest() { + private var startingMillis: Long = System.currentTimeMillis() + @Before + fun getIntoScreen() { + screen { + httpUrlConnectionButton.click() + startingMillis = System.currentTimeMillis() + } + } + + @Test + fun testGetRequest() { + screen { + successfulGetButton.click() + waitForSendingData() + } + + NetworkRequestsUtil.verifyBySpanNameWithZipkin( + method = "GET", + duration = ZipkinCommunicator.getTestDuration(startingMillis) + ) + } + + @Test + fun testPostRequest() { + screen { + postButton.click() + waitForSendingData() + } + + NetworkRequestsUtil.verifyBySpanNameWithZipkin( + method = "POST", + duration = ZipkinCommunicator.getTestDuration(startingMillis) + ) + } +} diff --git a/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/NetworkRequestsUtil.kt b/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/NetworkRequestsUtil.kt new file mode 100644 index 000000000..73dcda3e4 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/NetworkRequestsUtil.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.tests.component.networkrequests + +import android.util.Log +import com.smartlook.app.lib.ZipkinCommunicator + +object NetworkRequestsUtil { + fun verifyBySpanNameWithZipkin(method: String, duration: Long) { + val traces = ZipkinCommunicator.getAllTracesBySpanName(method.lowercase(), duration) + Log.i("TRACES", traces) + + assert( + traces.contains( + """ + "http.method":"${method.uppercase()}" + """.trimIndent() + ) + ) + + assert( + traces.contains( + """ + "name":"${method.lowercase()}" + """.trimIndent() + ) + ) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/OkHttp3Test.kt b/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/OkHttp3Test.kt new file mode 100644 index 000000000..6668bafb4 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/tests/component/networkrequests/OkHttp3Test.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.tests.component.networkrequests + +import com.smartlook.app.lib.ZipkinCommunicator +import com.smartlook.app.screens.MainScreen +import com.smartlook.app.screens.OkHttpScreen +import com.smartlook.app.screens.screen +import com.smartlook.app.tests.component.BaseTest +import org.junit.Before +import org.junit.Test + +class OkHttp3Test : BaseTest() { + private var startingMillis: Long = System.currentTimeMillis() + + @Before + fun getIntoOkHttpScreen() { + screen { + okHttpSampleCallsButton.click() + } + } + + @Test + fun testGetRequest() { + screen { + asynchronousGetButton.click() + waitForSendingData() + } + + NetworkRequestsUtil.verifyBySpanNameWithZipkin( + method = "GET", + duration = ZipkinCommunicator.getTestDuration(startingMillis) + ) + } + + @Test + fun testPostRequest() { + screen { + postMarkdownButton.click() + waitForSendingData() + } + + NetworkRequestsUtil.verifyBySpanNameWithZipkin( + method = "POST", + duration = ZipkinCommunicator.getTestDuration(startingMillis) + ) + } +} diff --git a/app/src/androidTest/java/com/smartlook/app/tests/e2e/BaseTestE2E.kt b/app/src/androidTest/java/com/smartlook/app/tests/e2e/BaseTestE2E.kt new file mode 100644 index 000000000..de6f2ed8b --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/tests/e2e/BaseTestE2E.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.tests.e2e + +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.smartlook.app.ui.MainActivity +import org.junit.Rule + +abstract class BaseTestE2E { + @get:Rule + val scenario = ActivityScenarioRule(MainActivity::class.java) +} diff --git a/app/src/androidTest/java/com/smartlook/app/tests/e2e/networkrequests/NetworkRequestsTest.kt b/app/src/androidTest/java/com/smartlook/app/tests/e2e/networkrequests/NetworkRequestsTest.kt new file mode 100644 index 000000000..1c640aaa4 --- /dev/null +++ b/app/src/androidTest/java/com/smartlook/app/tests/e2e/networkrequests/NetworkRequestsTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.tests.e2e.networkrequests + +import com.smartlook.app.screens.HttpUrlConnectionScreen +import com.smartlook.app.screens.MainScreen +import com.smartlook.app.screens.OkHttpScreen +import com.smartlook.app.screens.screen +import com.smartlook.app.tests.e2e.BaseTestE2E +import org.junit.Test + +class NetworkRequestsTest: BaseTestE2E() { + + @Test + fun selectSeveralNetworkRequests() { + screen { okHttpSampleCallsButton.click() } + + screen { + asynchronousGetButton.click() + synchronousGetButton.click() + postMarkdownButton.click() + postFileButton.click() + goBack() + } + + screen { httpUrlConnectionButton.click() } + + screen { + getCustomUrl("https://mock.httpstatus.io/200") + getCustomUrl("https://mock.httpstatus.io/401") + getCustomUrl("https://mock.httpstatus.io/404") + getCustomUrl("https://mock.httpstatus.io/503") + + postButton.click() + + waitForSendingData() + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a7808e753 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/smartlook/app/App.kt b/app/src/main/java/com/smartlook/app/App.kt new file mode 100644 index 000000000..f35d5fdda --- /dev/null +++ b/app/src/main/java/com/smartlook/app/App.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app + +import android.app.Application +import com.cisco.android.rum.integration.agent.api.AgentConfiguration +import com.cisco.android.rum.integration.agent.api.CiscoRUMAgent +import com.cisco.android.rum.integration.recording.api.SessionReplayModuleConfiguration +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import java.net.URL + +class App : Application() { + override fun onCreate() { + super.onCreate() + + // TODO: Reenable with the bridge support + // BridgeManager.bridgeInterfaces += TomasBridgeInterface() + + val agentConfig = AgentConfiguration( + url = URL("https://alameda-eum-qe.saas.appd-test.com"), + appName = "smartlook-android", + appVersion = "0.1" + ) + + val agent = CiscoRUMAgent.install( + application = this, + agentConfiguration = agentConfig, + moduleConfigurations = arrayOf( + SessionReplayModuleConfiguration() + ) + ) + + // MARK temp comment + // agent.sessionReplay.preferences.renderingMode = RenderingMode.NATIVE + agent.sessionReplay.start() + } +} diff --git a/app/src/main/java/com/smartlook/app/bridge/TomasBridgeInterface.kt b/app/src/main/java/com/smartlook/app/bridge/TomasBridgeInterface.kt new file mode 100644 index 000000000..1f08d4e23 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/bridge/TomasBridgeInterface.kt @@ -0,0 +1,212 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO out of scope for GA +//package com.smartlook.app.bridge +// +//import android.animation.ArgbEvaluator +//import android.graphics.Rect +//import android.view.View +//import com.smartlook.app.view.bridge.TomasElement +//import com.smartlook.app.view.bridge.TomasView +//import com.smartlook.sdk.bridge.model.BridgeFrameworkInfo +//import com.smartlook.sdk.bridge.model.BridgeInterface +//import com.smartlook.sdk.bridge.model.BridgeWireframe +//import com.smartlook.sdk.common.utils.Colors +//import com.smartlook.sdk.common.utils.extensions.safeSubmit +//import java.util.concurrent.Executors +// +//class TomasBridgeInterface : BridgeInterface { +// +// private val executor = Executors.newCachedThreadPool() +// private val classes = listOf(TomasView::class.java) +// +// override var isRecordingAllowed: Boolean = true +// private set +// +// override fun obtainFrameworkInfo(callback: (BridgeFrameworkInfo?) -> Unit) { +// callback( +// BridgeFrameworkInfo( +// framework = "TomasBridgeInterface", +// frameworkPluginVersion = "1.0", +// frameworkVersion = "0.1" +// ) +// ) +// } +// +// override fun obtainWireframeRootClasses(): List> { +// return classes +// } +// +// override fun obtainWireframeData(instance: View, callback: (BridgeWireframe?) -> Unit) { +// if (instance !is TomasView) { +// callback(null) +// return +// } +// +// instance.listener = tomasViewListener +// +// executor.safeSubmit { +// simulatePerformanceDemanding() +// +// val bridgeWireframe = extractBridgeWireframe(instance) +// callback(bridgeWireframe) +// } +// } +// +// private fun simulatePerformanceDemanding() { +// val delay = (2 + Math.random() * 3).toLong() +// Thread.sleep(delay) +// } +// +// private fun extractBridgeWireframe(view: TomasView): BridgeWireframe { +// val subviews = ArrayList() +// +// for (element in view.elements) +// subviews += when (element) { +// is TomasElement.Circle -> +// describeCircle(element) +// is TomasElement.Rectangle -> +// describeRectangle(element) +// is TomasElement.GradientRectangle -> +// describeGradientRectangle(element) +// } +// +// return BridgeWireframe( +// root = BridgeWireframe.View( +// id = "_bridgeRoot", +// name = null, +// rect = Rect(0, 0, view.width, view.height), +// type = null, +// typename = "TomasRootElement", +// hasFocus = false, +// offset = null, +// alpha = 1f, +// isSensitive = false, +// skeletons = null, +// foregroundSkeletons = null, +// subviews = subviews +// ), +// width = view.width, +// height = view.height +// ) +// } +// +// private fun describeCircle(element: TomasElement.Circle): BridgeWireframe.View { +// val rect = Rect(element.x - element.radius, element.y - element.radius, element.x + element.radius, element.y + element.radius) +// +// return BridgeWireframe.View( +// id = "_${element.hashCode()}", +// name = null, +// rect = rect, +// type = null, +// typename = "Circle", +// hasFocus = false, +// offset = null, +// alpha = 1f, +// isSensitive = element.isSensitive, +// skeletons = listOf( +// BridgeWireframe.View.Skeleton( +// colors = Colors(element.color), +// radius = 0, +// type = null, +// rect = Rect(rect), +// flags = null, +// isOpaque = false +// ) +// ), +// foregroundSkeletons = null, +// subviews = null +// ) +// } +// +// private fun describeRectangle(element: TomasElement.Rectangle): BridgeWireframe.View { +// return BridgeWireframe.View( +// id = "_${element.hashCode()}", +// name = null, +// rect = Rect(element.rect), +// type = null, +// typename = "Rectangle", +// hasFocus = false, +// offset = null, +// alpha = 1f, +// isSensitive = element.isSensitive, +// skeletons = listOf( +// BridgeWireframe.View.Skeleton( +// colors = Colors(element.color), +// radius = 0, +// type = null, +// rect = Rect(element.rect), +// flags = null, +// isOpaque = true +// ) +// ), +// foregroundSkeletons = null, +// subviews = null +// ) +// } +// +// private fun describeGradientRectangle(element: TomasElement.GradientRectangle): BridgeWireframe.View { +// val p25 = ARGB_EVALUATOR.evaluate(0.25f, element.topLeftColor, element.bottomRightColor) as Int +// val p50 = ARGB_EVALUATOR.evaluate(0.5f, element.topLeftColor, element.bottomRightColor) as Int +// val p75 = ARGB_EVALUATOR.evaluate(0.75f, element.topLeftColor, element.bottomRightColor) as Int +// +// val colors = Colors(3, 3) +// colors[0, 0] = element.topLeftColor +// colors[1, 0] = p25 +// colors[2, 0] = p50 +// colors[0, 1] = p25 +// colors[1, 1] = p50 +// colors[2, 1] = p50 +// colors[0, 2] = p50 +// colors[1, 2] = p75 +// colors[2, 2] = element.bottomRightColor +// +// return BridgeWireframe.View( +// id = "_${element.hashCode()}", +// name = null, +// rect = Rect(element.rect), +// type = null, +// typename = "Rectangle", +// hasFocus = false, +// offset = null, +// alpha = 1f, +// isSensitive = element.isSensitive, +// skeletons = listOf( +// BridgeWireframe.View.Skeleton( +// colors = colors, +// radius = 0, +// type = null, +// rect = Rect(element.rect), +// flags = null, +// isOpaque = true +// ) +// ), +// foregroundSkeletons = null, +// subviews = null +// ) +// } +// +// private val tomasViewListener = object : TomasView.Listener { +// override fun onTransitionChanged(isRunning: Boolean) { +// isRecordingAllowed = !isRunning +// } +// } +// +// private companion object { +// val ARGB_EVALUATOR = ArgbEvaluator() +// } +//} diff --git a/app/src/main/java/com/smartlook/app/extension/ContextExt.kt b/app/src/main/java/com/smartlook/app/extension/ContextExt.kt new file mode 100644 index 000000000..186161fc6 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/extension/ContextExt.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.extension + +import android.content.Context +import android.util.DisplayMetrics +import android.util.Size +import android.view.WindowManager + +val Context.screenSize: Size + get() { + val manager = getSystemService(Context.WINDOW_SERVICE) as WindowManager + val metrics = DisplayMetrics() + manager.defaultDisplay.getMetrics(metrics) + return Size(metrics.widthPixels, metrics.heightPixels) + } diff --git a/app/src/main/java/com/smartlook/app/extension/FragmentTransactionExt.kt b/app/src/main/java/com/smartlook/app/extension/FragmentTransactionExt.kt new file mode 100644 index 000000000..897469f18 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/extension/FragmentTransactionExt.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.extension + +import androidx.fragment.app.FragmentTransaction +import com.smartlook.app.util.FragmentAnimation + +fun FragmentTransaction.setCustomAnimations(animation: FragmentAnimation?): FragmentTransaction { + if (animation != null) + return setCustomAnimations(animation.enter, animation.exit, animation.popEnter, animation.popExit) + + return this +} diff --git a/app/src/main/java/com/smartlook/app/extension/PointFExt.kt b/app/src/main/java/com/smartlook/app/extension/PointFExt.kt new file mode 100644 index 000000000..c1fa63031 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/extension/PointFExt.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.extension + +import android.graphics.PointF +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +private val ZERO = PointF(0f, 0f) + +fun PointF.rotate(angle: Float, pivot: PointF = ZERO): PointF { + val angleRad = angle * PI.toFloat() / 180f + + val cosTheta = cos(angleRad) + val sinTheta = sin(angleRad) + + val tempX = cosTheta * (x - pivot.x) - sinTheta * (y - pivot.y) + pivot.x + val tempY = sinTheta * (x - pivot.x) + cosTheta * (y - pivot.y) + pivot.y + + x = tempX + y = tempY + + return this +} + +fun PointF.add(offsetX: Float, offsetY: Float): PointF { + x += offsetX + y += offsetY + + return this +} diff --git a/app/src/main/java/com/smartlook/app/extension/ToolbarExt.kt b/app/src/main/java/com/smartlook/app/extension/ToolbarExt.kt new file mode 100644 index 000000000..7eb99dc4c --- /dev/null +++ b/app/src/main/java/com/smartlook/app/extension/ToolbarExt.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.extension + +import androidx.annotation.StringRes +import androidx.appcompat.widget.Toolbar + +fun Toolbar.setSubtitle(@StringRes resId: Int?) { + if (resId != null) + setSubtitle(resId) + else + subtitle = "" +} diff --git a/app/src/main/java/com/smartlook/app/ui/BaseFragment.kt b/app/src/main/java/com/smartlook/app/ui/BaseFragment.kt new file mode 100644 index 000000000..f323e875b --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/BaseFragment.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding +import com.smartlook.app.util.FragmentAnimation + +abstract class BaseFragment : Fragment() { + + private val activity: MainActivity + get() = requireActivity() as MainActivity + + private var viewBindingInternal: T? = null + + protected abstract val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> T + + protected val viewBinding: T + get() = viewBindingInternal ?: error("Must be called after createView()") + + abstract val titleRes: Int + + open val subtitleRes: Int? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + viewBindingInternal = viewBindingCreator(inflater, container, false) + return viewBindingInternal?.root + } + + override fun onDestroyView() { + viewBindingInternal = null + super.onDestroyView() + } + + fun navigateTo(fragment: BaseFragment<*>, animation: FragmentAnimation? = null) { + activity.navigateTo(fragment, animation) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/MainActivity.kt b/app/src/main/java/com/smartlook/app/ui/MainActivity.kt new file mode 100644 index 000000000..9964079fa --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/MainActivity.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.smartlook.app.databinding.ActivityMainBinding +import com.smartlook.app.extension.setCustomAnimations +import com.smartlook.app.extension.setSubtitle +import com.smartlook.app.ui.menu.MenuFragment +import com.smartlook.app.util.FragmentAnimation +import com.smartlook.sdk.common.utils.extensions.contentView + +class MainActivity : AppCompatActivity() { + + private lateinit var viewBinding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewBinding = ActivityMainBinding.inflate(layoutInflater, contentView, true) + + setSupportActionBar(viewBinding.toolbar) + viewBinding.toolbar.setNavigationOnClickListener { navigateUp() } + + if (savedInstanceState == null) + navigateTo(MenuFragment()) + else + contentView?.post { updateToolbar() } + } + + override fun onBackPressed() { + navigateUp() + } + + fun navigateTo(fragment: BaseFragment<*>, animation: FragmentAnimation? = null) { + supportFragmentManager.beginTransaction() + .setCustomAnimations(animation) + .replace(viewBinding.container.id, fragment) + .addToBackStack(fragment.tag ?: fragment.hashCode().toString()) + .commit() + + contentView?.post { updateToolbar() } + } + + fun navigateUp() { + if (supportFragmentManager.backStackEntryCount > 1) { + supportFragmentManager.popBackStackImmediate() + updateToolbar() + } else + finish() + } + + private fun updateToolbar() { + val isBackButtonVisible = supportFragmentManager.backStackEntryCount > 1 + supportActionBar?.setDisplayHomeAsUpEnabled(isBackButtonVisible) + supportActionBar?.setDisplayShowHomeEnabled(isBackButtonVisible) + + val fragment = supportFragmentManager.fragments.last() as? BaseFragment<*> + if (fragment != null) { + viewBinding.toolbar.setTitle(fragment.titleRes) + viewBinding.toolbar.setSubtitle(fragment.subtitleRes) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/SampleFragment.kt b/app/src/main/java/com/smartlook/app/ui/SampleFragment.kt new file mode 100644 index 000000000..074c06950 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/SampleFragment.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import com.smartlook.app.databinding.ItemExampleBinding + +class SampleFragment : Fragment() { + + private lateinit var viewBinding: ItemExampleBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + viewBinding = ItemExampleBinding.inflate(inflater, container, false) + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.title.text = arguments?.getString("title") + viewBinding.description.text = arguments?.getString("description") + viewBinding.root.setBackgroundColor(arguments?.getInt("color") ?: Color.TRANSPARENT) + } + + companion object { + + fun create(title: String, description: String, color: Int): SampleFragment { + val fragment = SampleFragment() + fragment.arguments = bundleOf( + "title" to title, + "description" to description, + "color" to color + ) + + return fragment + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/adapter/SensitiveItemAdapter.kt b/app/src/main/java/com/smartlook/app/ui/adapter/SensitiveItemAdapter.kt new file mode 100644 index 000000000..b383f11e5 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/adapter/SensitiveItemAdapter.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.databinding.ItemSensitiveBinding + +class SensitiveItemAdapter( + context: Context, + private val count: Int +) : RecyclerView.Adapter() { + + private val inflater = LayoutInflater.from(context) + + override fun getItemCount(): Int { + return count + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { + val binding = ItemSensitiveBinding.inflate(inflater, parent, false) + return ItemViewHolder(binding) + } + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + holder.binding.a.isSensitive = true + holder.binding.b.isSensitive = true + holder.binding.c.isSensitive = true + + val context = holder.itemView.context + holder.binding.a.setOnClickListener { Toast.makeText(context, "A", Toast.LENGTH_SHORT).show() } + holder.binding.b.setOnClickListener { Toast.makeText(context, "B", Toast.LENGTH_SHORT).show() } + holder.binding.c.setOnClickListener { Toast.makeText(context, "C", Toast.LENGTH_SHORT).show() } + } + + class ItemViewHolder(val binding: ItemSensitiveBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/com/smartlook/app/ui/adapter/SimpleItemAdapter.kt b/app/src/main/java/com/smartlook/app/ui/adapter/SimpleItemAdapter.kt new file mode 100644 index 000000000..39873534a --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/adapter/SimpleItemAdapter.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.smartlook.app.databinding.ItemExampleBinding +import com.smartlook.app.ui.wireframe.model.SimpleItem + +class SimpleItemAdapter( + context: Context, + private val list: List +) : RecyclerView.Adapter() { + + private val inflater = LayoutInflater.from(context) + + override fun getItemCount(): Int { + return list.size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleItemViewHolder { + val binding = ItemExampleBinding.inflate(inflater, parent, false) + return SimpleItemViewHolder(binding) + } + + override fun onBindViewHolder(holder: SimpleItemViewHolder, position: Int) { + holder.bind(list[position]) + } + + class SimpleItemViewHolder( + private val binding: ItemExampleBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: SimpleItem) { + binding.title.text = item.title + binding.description.text = item.description + binding.root.setBackgroundColor(item.backgroundColor) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/adapter/SpinnerAdapter.kt b/app/src/main/java/com/smartlook/app/ui/adapter/SpinnerAdapter.kt new file mode 100644 index 000000000..7bf9d401b --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/adapter/SpinnerAdapter.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.databinding.ItemExampleBinding +import com.smartlook.app.ui.wireframe.model.SimpleItem + +class SpinnerAdapter(context: Context, items: List) : ArrayAdapter(context, 0, items) { + + private val inflater = LayoutInflater.from(context) + + @SuppressLint("ViewHolder") + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val binding = ItemExampleBinding.inflate(inflater, parent, false) + val item = getItem(position) ?: error("Null item") + + binding.title.text = item.title + binding.description.isSensitive = true + binding.description.text = item.description + binding.root.setBackgroundColor(item.backgroundColor) + + return binding.root + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View = getView(position, convertView, parent) +} diff --git a/app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter.kt b/app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter.kt new file mode 100644 index 000000000..2de294158 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.adapter + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter +import com.smartlook.app.ui.SampleFragment +import com.smartlook.app.util.randomColor + +class ViewPagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { + + override fun getCount(): Int { + return 10 + } + + override fun getItem(position: Int): Fragment { + return SampleFragment.create("Title $position", "Description $position", randomColor()) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter2.kt b/app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter2.kt new file mode 100644 index 000000000..3a2266968 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/adapter/ViewPagerAdapter2.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.adapter + +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.smartlook.app.ui.SampleFragment +import com.smartlook.app.util.randomColor + +class ViewPagerAdapter2(fragment: Fragment) : FragmentStateAdapter(fragment) { + + override fun getItemCount(): Int { + return 10 + } + + override fun createFragment(position: Int): Fragment { + return SampleFragment.create("Title $position", "Description $position", randomColor()) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/CameraComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/CameraComposeActivity.kt new file mode 100644 index 000000000..c37bd7d9c --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/CameraComposeActivity.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.CameraSelector +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import com.smartlook.app.ui.compose.theme.BasicTheme +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class CameraComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + Content() + } + } +} + +@Composable +private fun Content() { + BasicTheme { + val context = LocalContext.current + + var grantedPermission by remember { mutableStateOf(false) } + + val requestPermission = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission(), + onResult = { grantedPermission = it } + ) + + LaunchedEffect(Unit) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) + grantedPermission = true + else + requestPermission.launch(Manifest.permission.CAMERA) + } + + if (grantedPermission) + Box { + Camera() + } + } +} + +@Composable +private fun Camera() { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + + val preview = Preview.Builder() + .build() + + val previewView = remember { PreviewView(context) } + previewView.implementationMode = PreviewView.ImplementationMode.COMPATIBLE // TODO Investigate compatibility with PERFORMANCE option + + val cameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + + LaunchedEffect(Unit) { + val cameraProvider = context.getCameraProvider() + cameraProvider.unbindAll() + + try { + cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) + preview.setSurfaceProvider(previewView.surfaceProvider) + } catch (e: IllegalArgumentException) { + Log.e("Camera", "No camera available", e) + } + } + + AndroidView( + factory = { previewView }, + modifier = Modifier + .fillMaxSize() + ) +} + +private suspend fun Context.getCameraProvider(): ProcessCameraProvider { + return suspendCoroutine { continuation -> + val provider = ProcessCameraProvider.getInstance(this) + val listener = { continuation.resume(provider.get()) } + val executor = ContextCompat.getMainExecutor(this) + provider.addListener(listener, executor) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/DrawOrderComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/DrawOrderComposeActivity.kt new file mode 100644 index 000000000..35b8468d3 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/DrawOrderComposeActivity.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.smartlook.app.ui.compose.theme.BasicTheme + +class DrawOrderComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MyApp() + } + } +} + +@Preview(showBackground = true, widthDp = 320, heightDp = 320) +@Composable +private fun MyApp() { + BasicTheme { + TestDrawOrder() + } +} + +@Composable +private fun TestDrawOrder() { + Box( + modifier = Modifier + .fillMaxSize() + ) { + Box( + modifier = Modifier.align(Alignment.Center) + ) { + Column { + Text( + text = "sensitive text sensitive text sensitive text sensitive text sensitive text sensitive text sensitive text sensitive text sensitive text sensitive text", + modifier = Modifier + .padding(15.dp) + .sessionReplay( + id = "sensitive_text", + isSensitive = true + ) + .drawWithContent { + drawContent() + drawCircle(Color.Green) + } + .drawBehind { + drawRect(Color.Red) + }, + textAlign = TextAlign.Center + ) + Text( + text = "clear text clear text clear text clear text clear text clear text clear text clear text clear text clear text clear text clear text clear text clear text", + modifier = Modifier + .padding(15.dp) + .sessionReplay( + id = "clear_text" + ), + textAlign = TextAlign.Center, + color = Color.Blue + ) + } + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/DrawRecompositionComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/DrawRecompositionComposeActivity.kt new file mode 100644 index 000000000..54dcdc9fd --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/DrawRecompositionComposeActivity.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Checkbox +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.primarySurface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay + +class DrawRecompositionComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MyApp() + } + } +} + +@Preview +@Composable +private fun MyApp() { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + .sessionReplay( + id = "root" + ) + ) { + for (i in 0 until 5) + CheckboxRow( + text = "Checkbox $i", + modifier = Modifier.padding(vertical = 8.dp), + ) + } +} + +@Composable +private fun CheckboxRow( + text: String, + modifier: Modifier, +) { + val selected = rememberSaveable { mutableStateOf(false) } + + Surface( + shape = RoundedCornerShape(8.0.dp), + color = if (selected.value) MaterialTheme.colors.primarySurface else MaterialTheme.colors.secondary, + border = BorderStroke( + width = 1.dp, + color = if (selected.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ), + modifier = modifier + .clip( + shape = RoundedCornerShape(8.0.dp) + ) + .clickable( + onClick = { + selected.value = !selected.value + } + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + modifier = Modifier.weight(1f) + ) + Box( + modifier = Modifier.padding(8.dp) + ) { + Checkbox( + checked = selected.value, + onCheckedChange = null + ) + } + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/ListComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/ListComposeActivity.kt new file mode 100644 index 000000000..2a647e829 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/ListComposeActivity.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.annotation.SuppressLint +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.Icons.Filled +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.Image +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.smartlook.app.ui.compose.theme.BasicTheme +import com.smartlook.app.view.BarChartView +import com.smartlook.app.view.CanvasElementsView + +class ListComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MyApp() + } + } +} + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@Preview(showBackground = true, widthDp = 320, heightDp = 320, uiMode = UI_MODE_NIGHT_YES) +@Preview(showBackground = true, widthDp = 320, heightDp = 320) +@Composable +private fun MyApp() { + BasicTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + Scaffold( + content = { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + TestList() + } + } + ) + } + } +} + +@Composable +private fun TestList(items: List = List(1000) { it }) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding( + vertical = 4.dp + ), + contentPadding = PaddingValues(all = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(items = items) { item -> + Item(item = item) + } + } +} + +@Composable +private fun Item(item: Int) { + Card( + backgroundColor = MaterialTheme.colors.primary, + modifier = Modifier + .padding( + vertical = 4.dp, + horizontal = 8.dp + ) + ) { + CardContent(item) + } +} + +@Composable +private fun CardContent(item: Int) { + var expanded by remember { mutableStateOf(false) } + + Row( + modifier = Modifier + .padding(12.dp) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(12.dp) + ) { + Text(text = "Hello, ") + Text( + text = item.toString(), + style = MaterialTheme.typography.h4.copy( + fontWeight = FontWeight.ExtraBold + ) + ) + if (expanded) { + Text( + text = "Composem ipsum color sit lazy, padding theme elit, sed do bouncy. ".repeat(4), + modifier = Modifier + .sessionReplay( + id = "text", + isSensitive = true + ) + ) + AndroidView( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .padding(top = 20.dp), + factory = ::BarChartView + ) + AndroidView( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .padding(top = 20.dp) + .sessionReplay( + id = "CanvasElementsView", + isSensitive = true + ), + factory = ::CanvasElementsView + ) + Image( + imageVector = Icons.Default.Image, + contentDescription = "Image", + colorFilter = ColorFilter.tint(Color(0xFF772B2B)) + ) + } + } + IconButton( + onClick = { expanded = !expanded } + ) { + Icon( + imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore, + contentDescription = if (expanded) "Show less" else "Show more" + ) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/MeasureRecompositionComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/MeasureRecompositionComposeActivity.kt new file mode 100644 index 000000000..1c7458562 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/MeasureRecompositionComposeActivity.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.core.os.postDelayed +import com.smartlook.sdk.common.utils.extensions.contentView + +class MeasureRecompositionComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + var lines by remember { mutableStateOf(2) } + + Handler(Looper.getMainLooper()).postDelayed(2000) { + lines = 30 + } + + Column( + modifier = Modifier + .padding(20.dp) + .fillMaxWidth() + .background(Color.Red) + ) { + (0..lines).forEach { + Box( + modifier = Modifier + .padding(5.dp) + ) { + Box( + modifier = Modifier + .background(Color.Blue) + .fillMaxWidth() + .height(5.dp) + ) + } + } + } + } + + contentView?.getChildAt(0)?.setBackgroundColor(0xff00ff00.toInt()) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/SurfaceComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/SurfaceComposeActivity.kt new file mode 100644 index 000000000..703f3525f --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/SurfaceComposeActivity.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.smartlook.app.ui.compose.theme.BasicTheme +import com.smartlook.app.view.SimpleGLSurfaceView + +class SurfaceComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + Content() + } + } +} + +@Composable +private fun Content() { + BasicTheme { + Box { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .verticalScroll( + state = scrollState + ) + .fillMaxSize() + .padding(20.dp) + ) { + AndroidView( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .padding(top = 20.dp), + factory = ::SimpleGLSurfaceView + ) + Text( + modifier = Modifier + .sessionReplay( + id = "label", + isSensitive = true + ) + .align(Alignment.CenterHorizontally) + .padding( + top = 20.dp + ) + .height(1500.dp), + text = "↓↓↓ Scroll down ↓↓↓" + ) + Button( + modifier = Modifier + .fillMaxWidth(), + onClick = {} + ) { + Text(text = "Click") + } + } + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/TextFieldComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/TextFieldComposeActivity.kt new file mode 100644 index 000000000..18d938a27 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/TextFieldComposeActivity.kt @@ -0,0 +1,200 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.content.res.Configuration +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.smartlook.app.ui.compose.theme.BasicTheme + +class TextFieldComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MyApp() + } + } +} + +@Preview(showBackground = true, widthDp = 320, heightDp = 320, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(showBackground = true, widthDp = 320, heightDp = 320) +@Composable +private fun MyApp() { + BasicTheme { + Column( + modifier = Modifier + .padding(20.dp), + ) { + Text( + modifier = Modifier + .fillMaxWidth(), + text = "Single line" + ) + + var singleLineText by remember { mutableStateOf("") } + + TextField( + value = singleLineText, + modifier = Modifier + .padding( + top = 20.dp + ) + .border( + width = 2.dp, + color = Color.Black, + shape = RoundedCornerShape( + size = 10.dp + ) + ) + .background(Color.Transparent) + .fillMaxWidth(), + placeholder = { + Text( + text = "Enter a text", + ) + }, + onValueChange = { + singleLineText = it + }, + textStyle = MaterialTheme.typography.body1, + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + placeholderColor = Color.Gray, + cursorColor = Color.Blue + ), + singleLine = true + ) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding( + top = 20.dp + ), + text = "Multi line - fixed height" + ) + + var multiLineText by remember { mutableStateOf("") } + + TextField( + value = multiLineText, + modifier = Modifier + .padding( + top = 20.dp + ) + .border( + width = 2.dp, + color = Color.Black, + shape = RoundedCornerShape( + size = 10.dp + ) + ) + .background(Color.Transparent) + .height(100.dp) + .fillMaxWidth(), + placeholder = { + Text( + text = "Enter a text", + ) + }, + onValueChange = { + multiLineText = it + }, + textStyle = MaterialTheme.typography.body1, + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + placeholderColor = Color.Gray, + cursorColor = Color.Blue + ) + ) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding( + top = 20.dp + ), + text = "Multi line - wrap height" + ) + + var multiLineTextWrapHeight by remember { mutableStateOf("") } + + TextField( + value = multiLineTextWrapHeight, + modifier = Modifier + .padding( + top = 20.dp + ) + .border( + width = 2.dp, + color = Color.Black, + shape = RoundedCornerShape( + size = 10.dp + ) + ) + .background(Color.Transparent) + .fillMaxWidth(), + placeholder = { + Text( + text = "Enter a text", + ) + }, + onValueChange = { + multiLineTextWrapHeight = it + }, + textStyle = MaterialTheme.typography.body1, + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + placeholderColor = Color.Gray, + cursorColor = Color.Blue + ) + ) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/VideoComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/VideoComposeActivity.kt new file mode 100644 index 000000000..05df9ddb7 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/VideoComposeActivity.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.ui.StyledPlayerView +import com.google.android.exoplayer2.upstream.RawResourceDataSource +import com.smartlook.app.R +import com.smartlook.app.ui.compose.theme.BasicTheme + +class VideoComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + Content() + } + } +} + +@Composable +private fun Content() { + BasicTheme { + VideoView() + } +} + +@Composable +private fun VideoView() { + val context = LocalContext.current + + val mediaItem = MediaItem.Builder() + .setUri(RawResourceDataSource.buildRawResourceUri(R.raw.rainy_day)) + .build() + + val player = ExoPlayer.Builder(context) + .build() + + player.setMediaItem(mediaItem) + player.repeatMode = Player.REPEAT_MODE_ONE + player.prepare() + + DisposableEffect( + AndroidView( + factory = { + StyledPlayerView(it).also { + it.player = player + } + }, + modifier = Modifier + .aspectRatio(0.764f) + .sessionReplay() + ) + ) { + onDispose { player.release() } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/ViewDrawOrderComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/ViewDrawOrderComposeActivity.kt new file mode 100644 index 000000000..51070fbea --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/ViewDrawOrderComposeActivity.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose + +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.View +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.smartlook.app.ui.compose.theme.BasicTheme + +class ViewDrawOrderComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MyApp() + } + } +} + +@Preview(showBackground = true, widthDp = 320, heightDp = 320) +@Composable +private fun MyApp() { + BasicTheme { + TestSkeletonOrder() + } +} + +@Composable +private fun TestSkeletonOrder() { + Box( + modifier = Modifier + .fillMaxSize() + ) { + Column(modifier = Modifier.align(Alignment.Center)) { + Rectangles( + modifier = Modifier + .padding(top = 10.dp), + useSmartlookModifier = true + ) + + Rectangles( + modifier = Modifier + .padding(top = 10.dp), + useSmartlookModifier = false + ) + } + } +} + +@Composable +private fun Rectangles(modifier: Modifier, useSmartlookModifier: Boolean) { + Box(modifier = modifier) { + Box( + modifier = Modifier + .size( + width = 100.dp, + height = 100.dp + ) + .background( + color = Color.Red + ) + ) + AndroidView( + factory = { + View(it).apply { background = ColorDrawable(android.graphics.Color.GREEN) } + }, + modifier = Modifier + .matchParentSize() + .padding( + horizontal = 10.dp, + vertical = 10.dp + ).whether({ useSmartlookModifier }) { + sessionReplay() + } + ) + Box( + modifier = Modifier + .matchParentSize() + .padding( + horizontal = 20.dp, + vertical = 20.dp + ) + .background( + color = Color.Blue + ) + ) + + if (useSmartlookModifier) + Text( + text = "Smartlook", + color = Color.White, + fontSize = 10.sp, + modifier = Modifier + .align(Alignment.Center) + ) + } +} + +private fun T.whether(predicate: () -> Boolean, action: T.() -> T): T { + return if (predicate()) action() else this +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/WebViewComposeActivity.kt b/app/src/main/java/com/smartlook/app/ui/compose/WebViewComposeActivity.kt new file mode 100644 index 000000000..a83c70356 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/WebViewComposeActivity.kt @@ -0,0 +1,526 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalMaterialApi::class) + +package com.smartlook.app.ui.compose + +import android.annotation.SuppressLint +import android.app.Activity +import android.os.Bundle +import android.util.LayoutDirection +import android.view.ViewGroup +import android.webkit.WebChromeClient +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.ComponentActivity +import androidx.activity.compose.BackHandler +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.ModalBottomSheetDefaults +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ChevronLeft +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.text.layoutDirection +import androidx.core.widget.NestedScrollView +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.smartlook.app.ui.compose.theme.BasicTheme +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.util.Locale + +class WebViewComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + BasicTheme { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + onClick = { + showAsBottomSheet( + background = Color.White + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + BottomSheetWebViewContent( + modifier = Modifier.fillMaxSize(), + title = "My web View", + url = "https://getapp-test.astropaycard.com/terms-and-conditions/debit-card-terms.html" + ) + } + } + } + ) { + Text(text = "Open WebView in BottomSheet") + } + } + } + } + } +} + +// Code below copied from client +@Composable +private fun HeaderInfo( + title: String, + canGoForward: Boolean, + canGoBack: Boolean, + onClickPrevious: () -> Unit, + onClickNext: () -> Unit, + onBack: (() -> Unit)? = null +) { + Row(verticalAlignment = Alignment.CenterVertically) { + onBack?.let { + IconNavigationBack { it() } + } + Text( + text = title, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier + .weight(1f) + .padding(horizontal = 16.dp), + color = Color.Black + ) + + Row { + ArrowIndicator( + imageVector = Icons.Filled.ChevronLeft, + isEnable = canGoBack, + onClick = { + onClickPrevious.invoke() + } + ) + + ArrowIndicator( + imageVector = Icons.Filled.ChevronRight, + isEnable = canGoForward, + onClick = { + onClickNext.invoke() + } + ) + } + } +} + +@Stable +private fun Modifier.readDirection(): Modifier { + return if (Locale.getDefault().layoutDirection == LayoutDirection.RTL) + scale(scaleX = -1f, scaleY = 1f) + else + this +} + +@Composable +private fun ArrowIndicator( + isEnable: Boolean, + imageVector: ImageVector, + onClick: () -> Unit +) { + var modifier = Modifier + .readDirection() + .padding(8.dp) + + modifier = if (isEnable) + modifier + .alpha(1f) + .clickable { onClick.invoke() } + else + modifier.alpha(0.5f) + + Icon( + imageVector = imageVector, + modifier = modifier, + tint = Color.Black, + contentDescription = null, + ) +} + +@Composable +private fun IconNavigationBack( + modifier: Modifier = Modifier, + navAction: () -> Unit +) { + IconButton( + modifier = modifier, + onClick = navAction + ) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = null, + tint = Color.Black, + modifier = Modifier.readDirection() + ) + } +} + +@Composable +private fun Header( + title: String, + progress: Float, + canGoForward: Boolean, + canGoBack: Boolean, + onClickPrevious: () -> Unit, + onClickNext: () -> Unit, + onBack: (() -> Unit)? = null +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.height(8.dp)) + + HeaderInfo( + title = title, + onClickPrevious = onClickPrevious, + onClickNext = onClickNext, + canGoBack = canGoBack, + canGoForward = canGoForward, + onBack = onBack + ) + + Spacer(modifier = Modifier.height(12.dp)) + + if (progress / 100f < 1f) { + LinearProgressIndicator( + progress = progress / 100, + modifier = Modifier + .fillMaxWidth() + .height(4.dp), + color = Color.Black + ) + } + } +} + +@SuppressLint("SetJavaScriptEnabled") +@Composable +private fun BottomSheetWebViewContent( + modifier: Modifier, + title: String, + url: String, + back: (() -> Unit)? = null +) { + Column { + var webView by remember { mutableStateOf(null) } + var progress: Float by remember { mutableStateOf(0f) } + + val scrollState = rememberScrollState() + val scope = rememberCoroutineScope() + + val isPreview = LocalInspectionMode.current + + Header( + title = title, + onClickNext = { + webView?.goForward() + }, + onClickPrevious = { + webView?.goBack() + }, + progress = progress, + canGoBack = webView?.canGoBack() ?: false, + canGoForward = webView?.canGoForward() ?: false, + onBack = back + ) + + AndroidView( + factory = { context -> + val web = WebView(context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + scope.launch { scrollState.scrollTo(0) } + super.onPageFinished(view, url) + } + } + + if (!isPreview) { + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + settings.builtInZoomControls = true + settings.displayZoomControls = false + } + + webChromeClient = MyWebViewClient( + updateProgress = { + progress = it.toFloat() + } + ) + + webView = this + } + + NestedScrollView(context).apply { + addView(web) + web.loadUrl(url) + } + }, + modifier = modifier // TODO Investigate sensitivity bounds + .height(IntrinsicSize.Max) + .clipToBounds() + .verticalScroll(scrollState) + .sessionReplay( + id = "WebView" + ) + ) + } +} + +private class MyWebViewClient(val updateProgress: (Int) -> Unit) : WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + updateProgress(newProgress) + super.onProgressChanged(view, newProgress) + } +} + +private fun Activity.showAsBottomSheet( + background: Color, + scrimColor: Color? = null, + showDivider: Boolean = true, + showClose: Boolean = false, + content: @Composable ( + () -> Unit + ) -> Unit +) { + val viewGroup = this.findViewById(android.R.id.content) as ViewGroup + addContentToView(viewGroup, background, scrimColor, showDivider, showClose, content) +} + +private fun addContentToView( + viewGroup: ViewGroup, + background: Color, + scrimColor: Color?, + showDivider: Boolean = true, + showClose: Boolean = false, + content: @Composable (() -> Unit) -> Unit +) { + viewGroup.addView( + ComposeView(viewGroup.context).apply { + setContent { + BasicTheme { + BottomSheetWrapper(viewGroup, this, background, scrimColor, showDivider, showClose, content) + } + } + } + ) +} + +@Composable +private fun BottomSheetWrapper( + parent: ViewGroup, + composeView: ComposeView, + background: Color, + scrimColor: Color?, + showDivider: Boolean = true, + showClose: Boolean = false, + content: @Composable (() -> Unit) -> Unit +) { + val coroutineScope = rememberCoroutineScope() + val modalBottomSheetState = + rememberModalBottomSheetState( + ModalBottomSheetValue.Hidden, + confirmStateChange = { + it != ModalBottomSheetValue.HalfExpanded + } + ) + var isSheetOpened by remember { mutableStateOf(false) } + + ModalBottomSheetLayout( + sheetBackgroundColor = Color.Transparent, + sheetState = modalBottomSheetState, + sheetContent = { + BottomSheetUIWrapper( + coroutineScope = coroutineScope, + modalBottomSheetState = modalBottomSheetState, + background = background, + showDivider = showDivider, + showClose = showClose + ) { + content { + animateHideBottomSheet(coroutineScope, modalBottomSheetState) + } + } + }, + scrimColor = scrimColor ?: ModalBottomSheetDefaults.scrimColor + ) {} + + // Take action based on hidden state + LaunchedEffect(modalBottomSheetState.currentValue) { + when (modalBottomSheetState.currentValue) { + ModalBottomSheetValue.Hidden -> { + when { + isSheetOpened -> parent.removeView(composeView) + else -> { + isSheetOpened = true + modalBottomSheetState.show() + } + } + } + + else -> {} + } + } + + var isBackEnabled by remember { mutableStateOf(true) } + + BackHandlerExtension( + enabled = isBackEnabled + ) { + animateHideBottomSheet(coroutineScope, modalBottomSheetState) + isBackEnabled = false + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun BottomSheetUIWrapper( + coroutineScope: CoroutineScope, + modalBottomSheetState: ModalBottomSheetState, + background: Color, + showDivider: Boolean = true, + showClose: Boolean = false, + content: @Composable () -> Unit +) { + Box( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) + .background(background) + ) { + Box(Modifier.padding(top = 25.dp)) { + content() + } + + if (showDivider) { + Divider( + color = Color.Black, + thickness = 5.dp, + modifier = Modifier + .padding(top = 15.dp) + .align(Alignment.TopCenter) + .width(40.dp) + .clip(RoundedCornerShape(50.dp)) + ) + } + + if (showClose) { + IconButton( + modifier = Modifier + .align(Alignment.TopEnd), + onClick = { + coroutineScope.launch { + modalBottomSheetState.hide() + } + } + ) { + Icon( + modifier = Modifier + .align(Alignment.TopEnd), + imageVector = Icons.Filled.Close, + contentDescription = "", + tint = Color.Black + ) + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +private fun animateHideBottomSheet( + coroutineScope: CoroutineScope, + modalBottomSheetState: ModalBottomSheetState +) { + coroutineScope.launch { + modalBottomSheetState.hide() + } +} + +@Composable +private fun BackHandlerExtension( + enabled: Boolean = true, + onBack: () -> Unit +) { + + if (!LocalInspectionMode.current) { + BackHandler(enabled = enabled, onBack = onBack) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/compose/theme/Color.kt b/app/src/main/java/com/smartlook/app/ui/compose/theme/Color.kt new file mode 100644 index 000000000..d6a639cd7 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/theme/Color.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose.theme + +import androidx.compose.ui.graphics.Color + +val Navy = Color(0xFF073042) +val Blue = Color(0xFF4285F4) +val LightBlue = Color(0xFFD7EFFE) +val Chartreuse = Color(0xFFEFF7CF) diff --git a/app/src/main/java/com/smartlook/app/ui/compose/theme/Theme.kt b/app/src/main/java/com/smartlook/app/ui/compose/theme/Theme.kt new file mode 100644 index 000000000..e8ed94ca1 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/compose/theme/Theme.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.compose.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val DarkColorPalette = darkColors( + surface = Blue, + onSurface = Navy, + primary = Navy, + onPrimary = Chartreuse +) + +private val LightColorPalette = lightColors( + surface = Blue, + onSurface = Color.White, + primary = LightBlue, + onPrimary = Navy +) + +@Composable +fun BasicTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + MaterialTheme( + colors = if (darkTheme) DarkColorPalette else LightColorPalette, + typography = MaterialTheme.typography, + shapes = MaterialTheme.shapes, + content = content + ) +} diff --git a/app/src/main/java/com/smartlook/app/ui/dialog/AlertDialog.kt b/app/src/main/java/com/smartlook/app/ui/dialog/AlertDialog.kt new file mode 100644 index 000000000..f42e0cd96 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/dialog/AlertDialog.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.dialog + +import android.app.AlertDialog +import android.content.Context +import android.view.LayoutInflater +import com.smartlook.app.databinding.FragmentDialogBinding + +object AlertDialog { + + fun show(context: Context, isDimEnabled: Boolean = true) { + val inflater = LayoutInflater.from(context) + val viewBinding = FragmentDialogBinding.inflate(inflater, null, false) + + val dialog = AlertDialog.Builder(context) + .setView(viewBinding.root) + .show() + + viewBinding.button.setOnClickListener { dialog.dismiss() } + + if (!isDimEnabled) + dialog.window?.setDimAmount(0f) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/dialog/BottomSheetDialogFragment.kt b/app/src/main/java/com/smartlook/app/ui/dialog/BottomSheetDialogFragment.kt new file mode 100644 index 000000000..c0339cd24 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/dialog/BottomSheetDialogFragment.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.dialog + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.smartlook.app.databinding.FragmentDialogBinding + +class BottomSheetDialogFragment : BottomSheetDialogFragment() { + + private lateinit var viewBinding: FragmentDialogBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + viewBinding = FragmentDialogBinding.inflate(inflater, container, false) + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.button.setOnClickListener { dismiss() } + viewBinding.sampleEditText.isSensitive = true + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/dialog/DialogActivity.kt b/app/src/main/java/com/smartlook/app/ui/dialog/DialogActivity.kt new file mode 100644 index 000000000..bbd798c00 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/dialog/DialogActivity.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.dialog + +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.databinding.FragmentDialogBinding +import com.smartlook.sdk.common.utils.extensions.contentView + +class DialogActivity : FragmentActivity() { + + private lateinit var viewBinding: FragmentDialogBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewBinding = FragmentDialogBinding.inflate(layoutInflater, contentView, true) + viewBinding.button.setOnClickListener { finishAfterTransition() } + viewBinding.sampleEditText.isSensitive = true + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/dialog/DialogFragment.kt b/app/src/main/java/com/smartlook/app/ui/dialog/DialogFragment.kt new file mode 100644 index 000000000..918551680 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/dialog/DialogFragment.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.dialog + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import androidx.fragment.app.DialogFragment +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.databinding.FragmentDialogBinding + +class DialogFragment : DialogFragment() { + + private lateinit var viewBinding: FragmentDialogBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + dialog.window?.requestFeature(Window.FEATURE_NO_TITLE) + return dialog + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + viewBinding = FragmentDialogBinding.inflate(inflater, container, false) + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.button.setOnClickListener { dismiss() } + viewBinding.sampleEditText.isSensitive = true + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerDialog.kt b/app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerDialog.kt new file mode 100644 index 000000000..28c680801 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerDialog.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.dialog + +import android.content.Context +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.drawable.GradientDrawable +import android.view.Gravity +import android.view.LayoutInflater +import android.view.WindowManager +import android.widget.FrameLayout +import com.smartlook.app.databinding.FragmentDialogBinding +import com.smartlook.sdk.common.utils.dpToPxF +import com.smartlook.sdk.common.utils.extensions.windowManager + +object WindowManagerDialog { + + fun show(context: Context) { + val windowManager = context.windowManager + + val inflater = LayoutInflater.from(context) + val viewBinding = FragmentDialogBinding.inflate(inflater, null, false) + + val background = GradientDrawable() + background.setColor(Color.WHITE) + background.cornerRadius = dpToPxF(15f) + + val rootView = FrameLayout(context) + rootView.background = background + rootView.elevation = dpToPxF(20f) + rootView.addView(viewBinding.root) + + viewBinding.button.setOnClickListener { windowManager.removeView(rootView) } + + val type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL + val flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + val layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, type, flags, PixelFormat.TRANSPARENT) + layoutParams.gravity = Gravity.CENTER + + windowManager.addView(rootView, layoutParams) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerToast.kt b/app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerToast.kt new file mode 100644 index 000000000..253fe6db0 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/dialog/WindowManagerToast.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.dialog + +import android.content.Context +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.drawable.GradientDrawable +import android.os.Handler +import android.os.Looper +import android.view.Gravity +import android.view.WindowManager +import android.widget.TextView +import androidx.core.os.postDelayed +import androidx.core.view.setPadding +import com.smartlook.sdk.common.utils.dpToPx +import com.smartlook.sdk.common.utils.dpToPxF +import com.smartlook.sdk.common.utils.extensions.windowManager + +object WindowManagerToast { + + fun show(context: Context) { + val windowManager = context.windowManager + + val background = GradientDrawable() + background.setColor(Color.WHITE) + background.cornerRadius = dpToPxF(15f) + + val textView = TextView(context) + textView.text = "This is a custom Toast" + textView.background = background + textView.elevation = dpToPxF(20f) + textView.setPadding(dpToPx(10f)) + + val type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL + val flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + val layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, type, flags, PixelFormat.TRANSPARENT) + layoutParams.gravity = Gravity.CENTER + + windowManager.addView(textView, layoutParams) + + Handler(Looper.getMainLooper()).postDelayed(3000L) { + windowManager.removeView(textView) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/httpurlconnection/HttpURLConnectionFragment.kt b/app/src/main/java/com/smartlook/app/ui/httpurlconnection/HttpURLConnectionFragment.kt new file mode 100644 index 000000000..c0126aa0b --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/httpurlconnection/HttpURLConnectionFragment.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.httpurlconnection + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.smartlook.app.databinding.FragmentHttpUrlConnectionBinding +import com.smartlook.app.ui.BaseFragment +import com.smartlook.sdk.common.utils.runOnBackgroundThread +import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL +import android.util.Log +import android.widget.Toast +import com.cisco.android.rum.library.httpurlconnection.HttpUrlInstrumentationConfig +import com.smartlook.app.R +import com.smartlook.sdk.common.utils.runOnUiThread +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +class HttpURLConnectionFragment : BaseFragment() { + + override val titleRes: Int = R.string.httpurlconnection_title + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHttpUrlConnectionBinding + get() = FragmentHttpUrlConnectionBinding::inflate + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Schedule thread that closes un-ended spans for edge cases + val executor = Executors.newSingleThreadScheduledExecutor() + executor.scheduleWithFixedDelay( + HttpUrlInstrumentationConfig.getReportIdleConnectionRunnable(), + 0, + HttpUrlInstrumentationConfig.getReportIdleConnectionInterval(), + TimeUnit.MILLISECONDS + ) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewBinding.customUrlGet.setOnClickListener { customUrlGet() } + viewBinding.successfulGet.setOnClickListener { successfulGet() } + viewBinding.unSuccessfulGet.setOnClickListener { unSuccessfulGet() } + viewBinding.getWithoutInputStream.setOnClickListener { getWithoutInputStream() } + viewBinding.fourConcurrentGetRequests.setOnClickListener { fourConcurrentGetRequests() } + viewBinding.sustainedConnection.setOnClickListener { sustainedConnection() } + viewBinding.stalledRequest.setOnClickListener { stalledRequest() } + viewBinding.post.setOnClickListener { post() } + } + + /** + * Demonstrates a custom url HttpURLConnection GET request + */ + fun customUrlGet() { + executeGet(viewBinding.customUrl.text.toString()) + showDoneToast("customUrlGet") + } + + /** + * Demonstrates a successful HttpURLConnection GET request + */ + fun successfulGet() { + executeGet("http://httpbin.org/get") + showDoneToast("successfulGet") + } + + /** + * Demonstrates a HttpURLConnection GET request with no call to getInputStream + * and thereby requiring either the disconnect or the harvester thread (if disconnect is not called) + * to end the span. This test covers OPTIONS and TRACE requests too. + */ + fun getWithoutInputStream() { + executeGet("http://httpbin.org/get", false) + showDoneToast("getWithoutInputStream") + } + + /** + * Demonstrates four concurrent HttpURLConnection GET request (running on different threads in parallel) + * Helps test proper synchronization is achieved in our callback APIs code. + */ + fun fourConcurrentGetRequests() { + executeGet("http://httpbin.org/get") + executeGet("http://google.com") + executeGet("http://android.com") + executeGet("http://httpbin.org/headers") + showDoneToast("fourConcurrentGetRequests") + } + + /** + * Demonstrates a HttpURLConnection GET request with no call to getInputStream and disconnect + * and thereby requiring the harvester thread to end the span. + */ + fun sustainedConnection() { + executeGet("http://httpbin.org/get", false, false) + showDoneToast("sustainedConnection") + } + + /** + * Demonstrates a HttpURLConnection GET request with 20s wait after initial read + * and thereby requiring the harvester thread to end the span. + */ + fun stalledRequest() { + executeGet("http://httpbin.org/get", false, true, true) + showDoneToast("stalledRequest") + } + + private fun executeGet(inputUrl: String, getInputStream: Boolean = true, disconnect: Boolean = true, stallRequest: Boolean = false) { + runOnBackgroundThread { + var connection: HttpURLConnection? = null + try { + val url = URL(inputUrl) + connection = url.openConnection() as HttpURLConnection + + val responseCode = connection.responseCode + val responseMessage = connection.responseMessage + val readInput = if (getInputStream) connection.inputStream.bufferedReader().use { it.readText() } else "" + val readInputString = if (getInputStream) ("input Stream: " + readInput) else "" + + stallRequest.takeIf { it }?.let { Thread.sleep(20000) } + + Log.v(TAG, "response code: " + responseCode + " response message: " + responseMessage + readInputString) + } catch (e: Exception) { + e.printStackTrace() + } finally { + connection?.takeIf { disconnect }?.disconnect() + } + } + } + + /** + * Demonstrates an unsuccessful HttpURLConnection GET request + */ + fun unSuccessfulGet() { + runOnBackgroundThread { + val connection = URL("http://httpbin.org/status/404").openConnection() as HttpURLConnection + try { + val responseCode = connection.responseCode + val responseMessage = connection.responseMessage + val errorStream = connection.errorStream + val readError = errorStream.bufferedReader().use { it.readText() } + Log.v(TAG, "response code: " + responseCode + " response message: " + responseMessage + + " ErrorStream: " + readError) + showDoneToast("unSuccessfulGet") + } catch (e: IOException) { + e.printStackTrace() + } finally { + connection.disconnect() + } + } + } + + /** + * Demonstrates a HttpURLConnection POST request + */ + fun post() { + runOnBackgroundThread { + val connection = URL("http://httpbin.org/post").openConnection() as HttpURLConnection + connection.doOutput = true + connection.requestMethod = "POST" + try { + val outputStream = connection.outputStream + outputStream.bufferedWriter().use { out -> out.write("Writing test content to output stream!") } + + val responseCode = connection.responseCode + val responseMessage = connection.responseMessage + val inputStream = connection.inputStream + val readInput = inputStream.bufferedReader().use { it.readText() } + Log.v(TAG, "response code: " + responseCode + " response message: " + responseMessage + + " InputStream: " + readInput) + showDoneToast("post") + } catch (e: IOException) { + e.printStackTrace() + } finally { + connection.disconnect() + } + } + } + + private fun showDoneToast(methodName: String) { + runOnUiThread { + Toast.makeText(context, getString(R.string.http_toast, methodName), Toast.LENGTH_SHORT).show() + } + } + + companion object { + private const val TAG = "HttpURLConnection" + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/interaction/FocusActivity.kt b/app/src/main/java/com/smartlook/app/ui/interaction/FocusActivity.kt new file mode 100644 index 000000000..d4bdc16d7 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/interaction/FocusActivity.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.interaction + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.widget.EditText +import android.widget.FrameLayout +import com.smartlook.sdk.common.utils.dpToPx +import java.util.UUID + +class FocusActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val layout = FrameLayout(this) + val field = EditText(this) + + val layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, dpToPx(50f)) + layoutParams.topMargin = -dpToPx(200f) + (Math.random() * dpToPx(400f)).toInt() + layoutParams.gravity = Gravity.CENTER + + layout.addView(field, layoutParams) + + setContentView(layout) + + field.setText(UUID.randomUUID().toString()) + layout.setOnClickListener(onClickListener) + } + + private val onClickListener = View.OnClickListener { + startActivity(Intent(this, FocusActivity::class.java)) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/menu/MenuFragment.kt b/app/src/main/java/com/smartlook/app/ui/menu/MenuFragment.kt new file mode 100644 index 000000000..bd15714e3 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/menu/MenuFragment.kt @@ -0,0 +1,256 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.menu + +import android.content.Intent +import android.graphics.Rect +import android.graphics.RectF +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.cisco.android.rum.integration.agent.api.CiscoRUMAgent +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.cisco.android.rum.integration.recording.api.extension.sessionReplay +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentMenuBinding +import com.smartlook.app.extension.screenSize +import com.smartlook.app.ui.BaseFragment +import com.smartlook.app.ui.compose.CameraComposeActivity +import com.smartlook.app.ui.compose.DrawOrderComposeActivity +import com.smartlook.app.ui.compose.DrawRecompositionComposeActivity +import com.smartlook.app.ui.compose.ListComposeActivity +import com.smartlook.app.ui.compose.MeasureRecompositionComposeActivity +import com.smartlook.app.ui.compose.SurfaceComposeActivity +import com.smartlook.app.ui.compose.TextFieldComposeActivity +import com.smartlook.app.ui.compose.VideoComposeActivity +import com.smartlook.app.ui.compose.ViewDrawOrderComposeActivity +import com.smartlook.app.ui.compose.WebViewComposeActivity +import com.smartlook.app.ui.httpurlconnection.HttpURLConnectionFragment +import com.smartlook.app.ui.interaction.FocusActivity +import com.smartlook.app.ui.okhttp.OkHttpFragment +import com.smartlook.app.ui.screenshot.AnimationFragment +import com.smartlook.app.ui.screenshot.ScreenshotRegionsFragment +import com.smartlook.app.ui.screenshot.ScreenshotViewsFragment +import com.smartlook.app.ui.wireframe.BridgeInterfaceFragment +import com.smartlook.app.ui.wireframe.CollapsingLayoutFragment +import com.smartlook.app.ui.wireframe.EmptyActivity +import com.smartlook.app.ui.wireframe.ListFragment +import com.smartlook.app.ui.wireframe.WebViewFragment +import com.smartlook.app.ui.wireframe.WireframeViewsFragment +import com.smartlook.app.util.FragmentAnimation +import com.smartlook.sdk.common.utils.extensions.toRect + +class MenuFragment : BaseFragment() { + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentMenuBinding + get() = FragmentMenuBinding::inflate + + override val titleRes: Int = R.string.menu_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.screenshotViews.setOnClickListener(onClickListener) + viewBinding.screenshotRegions.setOnClickListener(onClickListener) + viewBinding.screenshotMasks.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationNone.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationFade1.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationFade2.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationFade.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationTranslate1.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationTranslate2.setOnClickListener(onClickListener) + viewBinding.screenshotFragmentAnimationTranslate.setOnClickListener(onClickListener) + viewBinding.interactionsFocus.setOnClickListener(onClickListener) + viewBinding.wireframeViews.setOnClickListener(onClickListener) + viewBinding.wireframeCollapsingLayout.setOnClickListener(onClickListener) + viewBinding.wireframeOrdering.setOnClickListener(onClickListener) + viewBinding.wireframeList.setOnClickListener(onClickListener) + viewBinding.wireframeWebView.setOnClickListener(onClickListener) + viewBinding.bridgeInterface.setOnClickListener(onClickListener) + viewBinding.composeList.setOnClickListener(onClickListener) + viewBinding.composeDrawOrder.setOnClickListener(onClickListener) + viewBinding.composeViewDrawOrder.setOnClickListener(onClickListener) + viewBinding.composeDrawRecomposition.setOnClickListener(onClickListener) + viewBinding.composeMeasureRecomposition.setOnClickListener(onClickListener) + viewBinding.composeSurface.setOnClickListener(onClickListener) + viewBinding.composeCamera.setOnClickListener(onClickListener) + viewBinding.composeVideo.setOnClickListener(onClickListener) + viewBinding.composeTextField.setOnClickListener(onClickListener) + viewBinding.composeWebView.setOnClickListener(onClickListener) + viewBinding.crashReportsIllegal.setOnClickListener(onClickListener) + viewBinding.crashReportsMainThread.setOnClickListener(onClickListener) + viewBinding.crashReportsInBackground.setOnClickListener(onClickListener) + viewBinding.crashReportsNoAppCode.setOnClickListener(onClickListener) + viewBinding.crashReportsNoStacktrace.setOnClickListener(onClickListener) + viewBinding.crashReportsOutOfMemoryError.setOnClickListener(onClickListener) + viewBinding.crashReportsWithChainedExceptions.setOnClickListener(onClickListener) + viewBinding.crashReportsNull.setOnClickListener(onClickListener) + viewBinding.anrEvent.setOnClickListener(onClickListener) + viewBinding.okhttpSampleCalls.setOnClickListener(onClickListener) + viewBinding.httpurlconnection.setOnClickListener(onClickListener) + viewBinding.wireframeViews.isSensitive = true + + // viewBinding.screenshotViews.smartlookId = "screenshot_views_button" + } + + private fun toggleMasks() { + CiscoRUMAgent.instance.sessionReplay.recordingMask = if (CiscoRUMAgent.instance.sessionReplay.recordingMask == null) { + val screenSize = requireContext().screenSize + val w = screenSize.width + val h = screenSize.height + + com.cisco.android.rum.integration.recording.api.RecordingMask( + listOf( + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + Rect(0, 0, w, h), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.COVERING + ), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + RectF(w * 0.1f, w * 0.1f, w * 0.9f, h - w * 0.1f).toRect(), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.ERASING + ), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + RectF(w * 0.2f, w * 0.2f, w * 0.45f, w * 0.5f).toRect(), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.COVERING + ), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + RectF(w * 0.55f, w * 0.2f, w * 0.8f, w * 0.5f).toRect(), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.COVERING + ), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + RectF(w * 0.05f, w * 0.3f, w * 0.95f, w * 0.4f).toRect(), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.ERASING + ), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + RectF(w * 0.475f, w * 0.15f, w * 0.525f, w * 0.55f).toRect(), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.COVERING + ), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element( + RectF(w * 0.4f, w * 0.25f, w * 0.6f, w * 0.45f).toRect(), + com.cisco.android.rum.integration.recording.api.RecordingMask.Element.Type.ERASING + ) + ) + ) + } else + null + } + + private val onClickListener = View.OnClickListener { + when (it.id) { + viewBinding.screenshotViews.id -> + navigateTo(ScreenshotViewsFragment(), FragmentAnimation.FADE) + viewBinding.screenshotRegions.id -> + navigateTo(ScreenshotRegionsFragment(), FragmentAnimation.FADE) + viewBinding.screenshotMasks.id -> + toggleMasks() + viewBinding.screenshotFragmentAnimationNone.id -> + navigateTo(AnimationFragment()) + viewBinding.screenshotFragmentAnimationFade1.id -> + navigateTo(AnimationFragment(), FragmentAnimation.SLOW_FADE_1) + viewBinding.screenshotFragmentAnimationFade2.id -> + navigateTo(AnimationFragment(), FragmentAnimation.SLOW_FADE_2) + viewBinding.screenshotFragmentAnimationFade.id -> + navigateTo(AnimationFragment(), FragmentAnimation.SLOW_FADE) + viewBinding.screenshotFragmentAnimationTranslate1.id -> + navigateTo(AnimationFragment(), FragmentAnimation.SLOW_TRANSLATE_1) + viewBinding.screenshotFragmentAnimationTranslate2.id -> + navigateTo(AnimationFragment(), FragmentAnimation.SLOW_TRANSLATE_2) + viewBinding.screenshotFragmentAnimationTranslate.id -> + navigateTo(AnimationFragment(), FragmentAnimation.SLOW_TRANSLATE) + viewBinding.wireframeViews.id -> + navigateTo(WireframeViewsFragment(), FragmentAnimation.FADE) + viewBinding.wireframeCollapsingLayout.id -> + navigateTo(CollapsingLayoutFragment(), FragmentAnimation.FADE) + viewBinding.wireframeOrdering.id -> + startActivity(Intent(requireContext(), EmptyActivity::class.java)) + viewBinding.wireframeList.id -> + navigateTo(ListFragment(), FragmentAnimation.FADE) + viewBinding.interactionsFocus.id -> + startActivity(Intent(requireContext(), FocusActivity::class.java)) + viewBinding.composeList.id -> + startActivity(Intent(requireContext(), ListComposeActivity::class.java)) + viewBinding.composeDrawOrder.id -> + startActivity(Intent(requireContext(), DrawOrderComposeActivity::class.java)) + viewBinding.composeViewDrawOrder.id -> + startActivity(Intent(requireContext(), ViewDrawOrderComposeActivity::class.java)) + viewBinding.composeDrawRecomposition.id -> + startActivity(Intent(requireContext(), DrawRecompositionComposeActivity::class.java)) + viewBinding.composeMeasureRecomposition.id -> + startActivity(Intent(requireContext(), MeasureRecompositionComposeActivity::class.java)) + viewBinding.composeSurface.id -> + startActivity(Intent(requireContext(), SurfaceComposeActivity::class.java)) + viewBinding.composeCamera.id -> + startActivity(Intent(requireContext(), CameraComposeActivity::class.java)) + viewBinding.composeVideo.id -> + startActivity(Intent(requireContext(), VideoComposeActivity::class.java)) + viewBinding.composeTextField.id -> + startActivity(Intent(requireContext(), TextFieldComposeActivity::class.java)) + viewBinding.composeWebView.id -> + startActivity(Intent(requireContext(), WebViewComposeActivity::class.java)) + viewBinding.wireframeWebView.id -> + navigateTo(WebViewFragment(), FragmentAnimation.FADE) + viewBinding.bridgeInterface.id -> + navigateTo(BridgeInterfaceFragment(), FragmentAnimation.FADE) + viewBinding.crashReportsIllegal.id -> + throw IllegalArgumentException("Illegal Argument Exception Thrown!") + viewBinding.crashReportsMainThread.id -> + throw RuntimeException("Crashing on main thread") + viewBinding.crashReportsInBackground.id -> + Thread { throw RuntimeException("Attempt to crash background thread") }.start() + viewBinding.crashReportsNoAppCode.id -> { + val e = java.lang.RuntimeException("No Application Code") + e.stackTrace = arrayOf( + StackTraceElement("android.fake.Crash", "crashMe", "NotARealFile.kt", 12), + StackTraceElement("android.fake.Class", "foo", "NotARealFile.kt", 34), + StackTraceElement("android.fake.Main", "main", "NotARealFile.kt", 56) + ) + throw e + } + viewBinding.crashReportsNoStacktrace.id -> { + val e = java.lang.RuntimeException("No Stack Trace") + e.stackTrace = arrayOfNulls(0) + throw e + } + viewBinding.crashReportsOutOfMemoryError.id -> { + val e = OutOfMemoryError("out of memory") + e.stackTrace = arrayOfNulls(0) + throw e + } + viewBinding.crashReportsWithChainedExceptions.id -> { + try { + throw NullPointerException("Simulated error in exception 1") + } catch (e: NullPointerException) { + throw IllegalArgumentException("Simulated error in exception 2", e) + } + } + viewBinding.crashReportsNull.id -> + throw NullPointerException("I am null!") + viewBinding.anrEvent.id -> { + try { + Thread.sleep(6000) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + } + viewBinding.okhttpSampleCalls.id -> + navigateTo(OkHttpFragment(), FragmentAnimation.FADE) + viewBinding.httpurlconnection.id -> + navigateTo(HttpURLConnectionFragment(), FragmentAnimation.FADE) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/okhttp/OkHttpFragment.kt b/app/src/main/java/com/smartlook/app/ui/okhttp/OkHttpFragment.kt new file mode 100644 index 000000000..fda70454e --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/okhttp/OkHttpFragment.kt @@ -0,0 +1,622 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.okhttp + +import android.graphics.Bitmap +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentOkhttpBinding +import com.smartlook.app.ui.BaseFragment +import com.smartlook.sdk.common.utils.extensions.safeSchedule +import com.smartlook.sdk.common.utils.runOnBackgroundThread +import com.smartlook.sdk.common.utils.runOnUiThread +import io.opentelemetry.api.trace.Span +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.trace.SdkTracerProvider +import okhttp3.Cache +import okhttp3.Call +import okhttp3.Callback +import okhttp3.FormBody +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import okio.BufferedSink +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors + +class OkHttpFragment : BaseFragment() { + + private val client: OkHttpClient by lazy { + OkHttpClient() + } + + val retryInterceptor = Interceptor { chain: Interceptor.Chain -> + val request = chain.request() + var response = chain.proceed(request) + + var tryCount = 0 + while (response.code == 503 && tryCount < 2) { + tryCount++ + + // retry the request + response.close() + response = chain.proceed(request) + } + return@Interceptor response + } + + private val retryClient: OkHttpClient by lazy { + OkHttpClient.Builder() + .retryOnConnectionFailure(true) + .addInterceptor( + retryInterceptor + ) + .build() + } + + private val cachedClient = OkHttpClient.Builder() + .cache(Cache(File(activity?.cacheDir, DISK_CACHE_FOLDER), DISK_CACHE_SIZE)) + .build() + + private val executor = Executors.newScheduledThreadPool(1) + + override val titleRes: Int = R.string.okhttp_title + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentOkhttpBinding + get() = FragmentOkhttpBinding::inflate + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.synchronousGet.setOnClickListener { synchronousGet() } + viewBinding.asynchronousGet.setOnClickListener { asynchronousGet() } + viewBinding.concurrentAsynchronousGet.setOnClickListener { concurrentAsynchronousGet() } + viewBinding.parentContextPropagationInAsyncGet.setOnClickListener { parentContextPropagationInAsyncGet() } + viewBinding.unSuccessfulGet.setOnClickListener { unsuccessfulGet() } + viewBinding.retryRequest.setOnClickListener { retryRequest() } + viewBinding.multipleHeaders.setOnClickListener { multipleHeaders() } + viewBinding.postMarkdown.setOnClickListener { postMarkdown() } + viewBinding.postStreaming.setOnClickListener { postStreaming() } + viewBinding.postFile.setOnClickListener { postFile() } + viewBinding.postFormParameters.setOnClickListener { postFormParameters() } + viewBinding.postMutlipartRequest.setOnClickListener { postMutlipartRequest() } + viewBinding.networkError.setOnClickListener { networkError() } + viewBinding.responseCaching.setOnClickListener { responseCaching() } + viewBinding.canceledCall.setOnClickListener { canceledCall() } + } + + /** + * Demonstrates a synchronous successful okhttp GET - Download a file, print its headers, and print its response body as a string. + */ + fun synchronousGet() { + + val request = Request.Builder() + .url("https://publicobject.com/helloworld.txt") + .build() + + runOnBackgroundThread { + + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + for ((name, value) in response.headers) { + Log.v(TAG, "$name: $value") + } + + Log.v(TAG, response.body?.string() ?: "null") + showDoneToast("synchronousGet") + } + } + } + + /** + * Demonstrates an asynchronous successful okhttp GET. + * Download a file on a worker thread, and get called back when the response is readable. + * The callback is made after the response headers are ready. + */ + fun asynchronousGet() { + val url = "https://httpbin.org/robots.txt" + performAsynchronousGet(url) + showDoneToast("asynchronousGet") + } + + /** + * Demonstrates multiple successful asynchronous okhttp GET operations to the same url in parallel. + */ + fun concurrentAsynchronousGet() { + val url = "https://httpbin.org/headers" + performAsynchronousGet(url) + performAsynchronousGet(url) + showDoneToast("concurrentAsynchronousGet") + } + + /** + * Called from other functions to perform asynchronous GET to a given url. + */ + fun performAsynchronousGet(url: String) { + val request = Request.Builder() + .url(url) + .build() + + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + response.use { + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + for ((name, value) in response.headers) { + println("$name: $value") + } + + Log.v(TAG, response.body?.string() ?: "null") + } + } + }) + } + + /** + * Demonstrates correct context propagation for asynchronous requests when parent context exists. + */ + + fun parentContextPropagationInAsyncGet() { + + val lock = CountDownLatch(1) + + val openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .build() + + val span = openTelemetry.getTracer("Test Tracer").spanBuilder("A Span").startSpan() + + span!!.makeCurrent().use { ignored -> + + val client: OkHttpClient = OkHttpClient.Builder() + .addInterceptor( + Interceptor { chain: Interceptor.Chain -> + val currentSpan = + Span.current().spanContext + // Verify context propagation. + if (span.spanContext.traceId == + currentSpan.traceId) { + Log.d( TAG, "Testing parent context propagation in async get - trace id's are same as expected.") + } else { + Log.e( TAG, "Testing parent context propagation in async get - trace id's are unexpectedly not same.") + } + chain.proceed(chain.request()) + }) + .build() + + val request = Request.Builder() + .url("https://publicobject.com/helloworld.txt") + .build() + + client.newCall(request).enqueue( + object : Callback { + override fun onFailure(call: Call, e: IOException) {} + override fun onResponse( + call: Call, response: Response + ) { + // Verify that the original caller's context is the current one here. + if (span == Span.current()) { + Log.d( TAG, "Testing parent context propagation in async get - contexts are same as expected.") + } else { + Log.e( TAG, "Testing parent context propagation in async get - Contexts are unexpectedly different.") + } + lock.countDown() + } + }) + } + lock.await() + span.end() + showDoneToast("parentContextPropagationInAsyncGet") + } + + /** + * Demonstrates an unsuccessful okhttp GET. + */ + fun unsuccessfulGet() { + val request = Request.Builder() + .url("http://httpbin.org/status/404") + .build() + + runOnBackgroundThread { + try { + client.newCall(request).execute().use { response -> + + for ((name, value) in response.headers) { + Log.v(TAG, "$name: $value") + } + + Log.v(TAG, response.body?.string() ?: "null") + + showDoneToast("unsuccessfulGet") + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + + /** + * Demonstrates a request i.e being retried two additional times. + * http.request.resend_count is being set on each retried request. + */ + fun retryRequest() { + val request = Request.Builder() + .url("https://httpbin.org/status/503") + .build() + + runOnBackgroundThread { + try { + retryClient.newCall(request).execute().use { response -> + + for ((name, value) in response.headers) { + Log.v(TAG, "$name: $value") + } + + Log.v(TAG, response.body?.string() ?: "null") + + showDoneToast("retryRequest") + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + + /** + * Typically HTTP headers work like a Map: each field has one value or none. + * But some headers permit multiple values, like Guava’s Multimap. + * For example, it’s legal and common for an HTTP response to supply multiple Vary headers. + */ + fun multipleHeaders() { + val request = Request.Builder() + .url("https://api.github.com/repos/square/okhttp/issues") + .header("User-Agent", "OkHttp Headers.java") + .addHeader("Accept", "application/json; q=0.5") + .addHeader("Accept", "application/vnd.github.v3+json") + .build() + + runOnBackgroundThread { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + Log.v(TAG, "Server: ${response.header("Server")}") + Log.v(TAG, "Date: ${response.header("Date")}") + Log.v(TAG, "Vary: ${response.headers("Vary")}") + showDoneToast("multipleHeaders") + } + } + } + + /** + * Use an HTTP POST to send a request body to a service. + * This example posts a markdown document to a web service that renders markdown as HTML. + */ + fun postMarkdown() { + val postBody = """ + |Releases + |-------- + | + | * _1.0_ May 6, 2013 + | * _1.1_ June 15, 2013 + | * _1.2_ August 11, 2013 + |""".trimMargin() + + val request = Request.Builder() + .url("https://api.github.com/markdown/raw") + .post(postBody.toRequestBody(MEDIA_TYPE_MARKDOWN)) + .build() + + runOnBackgroundThread { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + Log.v(TAG, (response.body?.string() ?: "null")) + showDoneToast("postMarkdown") + } + } + } + + /** + * Here we POST a request body as a stream. The content of this request body is being generated + * as it’s being written. This example streams directly into the Okio buffered sink. + */ + fun postStreaming() { + val requestBody = object : RequestBody() { + override fun contentType() = MEDIA_TYPE_MARKDOWN + + override fun writeTo(sink: BufferedSink) { + sink.writeUtf8("Numbers\n") + sink.writeUtf8("-------\n") + for (i in 2..997) { + sink.writeUtf8(String.format(" * $i = ${factor(i)}\n")) + } + } + + private fun factor(n: Int): String { + for (i in 2 until n) { + val x = n / i + if (x * i == n) return "${factor(x)} × $i" + } + return n.toString() + } + } + + val request = Request.Builder() + .url("https://api.github.com/markdown/raw") + .post(requestBody) + .build() + + runOnBackgroundThread { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + Log.v(TAG, response.body?.string() ?: "null") + showDoneToast("postStreaming") + } + } + } + + /** + * It’s easy to use a file as a request body. + */ + fun postFile() { + val file = File(activity?.filesDir, "README.md") + + file.writeText( + """ + |Releases + |-------- + | + | * _1.0_ May 6, 2013 + | * _1.1_ June 15, 2013 + | * _1.2_ August 11, 2013 + |""".trimMargin() + ) + + val request = Request.Builder() + .url("https://api.github.com/markdown/raw") + .post(file.asRequestBody(MEDIA_TYPE_MARKDOWN)) + .build() + + runOnBackgroundThread { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + Log.v(TAG, response.body?.string() ?: "null") + showDoneToast("postFile") + } + } + } + + /** + * Use FormBody.Builder to build a request body that works like an HTML
tag. + * Names and values will be encoded using an HTML-compatible form URL encoding. + */ + fun postFormParameters() { + val formBody = FormBody.Builder() + .add("search", "Jurassic Park") + .build() + val request = Request.Builder() + .url("https://en.wikipedia.org/w/index.php") + .post(formBody) + .build() + + runOnBackgroundThread { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + Log.v(TAG, response.body?.string() ?: "null") + showDoneToast("postFormParameters") + } + } + } + + /** + * MultipartBody.Builder can build sophisticated request bodies compatible with HTML file upload forms. + * Each part of a multipart request body is itself a request body, and can define its own headers. + * If present, these headers should describe the part body, such as its Content-Disposition. + * The Content-Length and Content-Type headers are added automatically if they’re available. + */ + fun postMutlipartRequest() { + // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image + val requestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("title", "Square Logo") + .addFormDataPart( + "image", "logo-square.png", + writeOutBitmapIntoFile(File(activity?.filesDir, "logo-square.png")).asRequestBody(MEDIA_TYPE_PNG) + ) + .build() + + val request = Request.Builder() + .header("Authorization", "Client-ID $IMGUR_CLIENT_ID") + .url("https://api.imgur.com/3/image") + .post(requestBody) + .build() + + runOnBackgroundThread { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code $response") + + Log.v(TAG, response.body?.string() ?: "null") + showDoneToast("postMutlipartRequest") + } + } + } + + /** + * Demonstrates a connection error which results in an exception. Span status is set to ERROR + */ + fun networkError() { + val request = Request.Builder() + .url("https://www.example.invalid") + .build() + + runOnBackgroundThread { + try { + client.newCall(request).execute() + showDoneToast("networkError") + } catch (e: IOException) { + e.printStackTrace() + } + } + } + + /** + * To cache responses, you’ll need a cache directory that you can read and write to, and a limit on the cache’s size. + * The cache directory should be private, and untrusted applications should not be able to read its contents! + * + * It is an error to have multiple caches accessing the same cache directory simultaneously. + * Most applications should call new OkHttpClient() exactly once, configure it with + * their cache, and use that same instance everywhere. Otherwise the two cache instances will + * stomp on each other, corrupt the response cache, and possibly crash your program. + * + * Response caching uses HTTP headers for all configuration. You can add request headers like + * Cache-Control: max-stale=3600 and OkHttp’s cache will honor them. + * Your webserver configures how long responses are cached with its own response headers, + * like Cache-Control: max-age=9600. There are cache headers to force a cached response, + * force a network response, or force the network response to be validated with a conditional GET. + */ + fun responseCaching() { + val request = Request.Builder() + .url("https://publicobject.com/helloworld.txt") + .build() + + runOnBackgroundThread { + val response1Body = cachedClient.newCall(request).execute().use { + if (!it.isSuccessful) throw IOException("Unexpected code $it") + + Log.v(TAG, "Response 1 response: $it") + Log.v(TAG, "Response 1 cache response: ${it.cacheResponse}") + Log.v(TAG, "Response 1 network response: ${it.networkResponse}") + return@use it.body?.string() + } + + val response2Body = cachedClient.newCall(request).execute().use { + if (!it.isSuccessful) throw IOException("Unexpected code $it") + + Log.v(TAG, "Response 2 response: $it") + Log.v(TAG, "Response 2 cache response: ${it.cacheResponse}") + Log.v(TAG, "Response 2 network response: ${it.networkResponse}") + return@use it.body?.string() + } + + Log.v(TAG, "Response 2 equals Response 1? " + (response1Body == response2Body)) + showDoneToast("responseCaching") + } + } + + /** + * Use Call.cancel() to stop an ongoing call immediately. If a thread is currently writing + * a request or reading a response, it will receive an IOException. + * Use this to conserve the network when a call is no longer necessary; for example when your + * user navigates away from an application. Both synchronous and asynchronous calls can be canceled. + */ + fun canceledCall() { + val request = Request.Builder() + .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay. + .build() + + val startNanos = System.nanoTime() + val call = client.newCall(request) + + // Schedule a job to cancel the call in 1 second. + executor.safeSchedule(1_000) { + Log.v(TAG, String.format("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f)) + call.cancel() + Log.v(TAG, String.format("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f)) + } + + Log.v(TAG, String.format("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f)) + runOnBackgroundThread { + try { + call.execute().use { response -> + Log.e( + TAG, + String.format( + "%.2f Call was expected to fail, but completed: %s%n", + (System.nanoTime() - startNanos) / 1e9f, response + ) + ) + } + } catch (e: IOException) { + Log.v( + TAG, + String.format( + "%.2f Call failed as expected: %s%n", + (System.nanoTime() - startNanos) / 1e9f, e + ) + ) + showDoneToast("canceledCall") + } + } + } + + private fun showDoneToast(methodName: String) { + runOnUiThread { + Toast.makeText(context, getString(R.string.http_toast, methodName), Toast.LENGTH_SHORT).show() + } + } + + private fun writeOutBitmapIntoFile(file: File): File { + val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) + + try { + FileOutputStream(file).use { out -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out) // bmp is your Bitmap instance + } + } catch (e: IOException) { + e.printStackTrace() + } + + return file + } + + companion object { + private const val TAG = "OkHttp" + private val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType() + + private const val DISK_CACHE_SIZE = 10L * 1024L * 1024L // 10 MiB + private const val DISK_CACHE_FOLDER = "http" + + /** + * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running + * these examples, please request your own client ID! https://api.imgur.com/oauth2 + */ + private const val IMGUR_CLIENT_ID = "9199fdef135c122" + private val MEDIA_TYPE_PNG = "image/png".toMediaType() + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/screenshot/AnimationFragment.kt b/app/src/main/java/com/smartlook/app/ui/screenshot/AnimationFragment.kt new file mode 100644 index 000000000..818f411c3 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/screenshot/AnimationFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.screenshot + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentTransactionsBinding +import com.smartlook.app.ui.BaseFragment + +class AnimationFragment : BaseFragment() { + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentTransactionsBinding + get() = FragmentTransactionsBinding::inflate + + override val titleRes: Int = R.string.screenshot_fragment_animation_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.sensitiveField.isSensitive = true + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotRegionsFragment.kt b/app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotRegionsFragment.kt new file mode 100644 index 000000000..e6097ab68 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotRegionsFragment.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.screenshot + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentScreenshotRegionsBinding +import com.smartlook.app.ui.BaseFragment + +class ScreenshotRegionsFragment : BaseFragment() { + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentScreenshotRegionsBinding + get() = FragmentScreenshotRegionsBinding::inflate + + override val titleRes: Int = R.string.screenshot_fragment_regions_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.region1a.isSensitive = true + viewBinding.region1b.isSensitive = true + viewBinding.region1c.isSensitive = true + viewBinding.region1d.isSensitive = true + viewBinding.region1e.isSensitive = true + viewBinding.region1f.isSensitive = true + viewBinding.region2.isSensitive = true + viewBinding.region3.isSensitive = true + viewBinding.region4.isSensitive = true + viewBinding.region5.isSensitive = true + viewBinding.region6.isSensitive = true + viewBinding.region7a.isSensitive = true + viewBinding.region7b.isSensitive = true + viewBinding.region8a.isSensitive = true + viewBinding.region8b.isSensitive = true + viewBinding.region9a.isSensitive = true + viewBinding.region9b.isSensitive = true + viewBinding.region10a.isSensitive = true + viewBinding.region10b.isSensitive = true + viewBinding.region11a.isSensitive = true + viewBinding.region11b.isSensitive = true + viewBinding.region11c.isSensitive = true + viewBinding.region11d.isSensitive = true + viewBinding.region11e.isSensitive = true + viewBinding.region11f.isSensitive = true + viewBinding.region12a.isSensitive = true + viewBinding.region12b.isSensitive = true + viewBinding.region12c.isSensitive = true + viewBinding.region12d.isSensitive = true + viewBinding.region12e.isSensitive = true + viewBinding.region12f.isSensitive = true + viewBinding.region13a.isSensitive = true + viewBinding.region13b.isSensitive = true + viewBinding.region13c.isSensitive = true + viewBinding.region13d.isSensitive = true + viewBinding.region13e.isSensitive = true + viewBinding.region13f.isSensitive = true + viewBinding.region14a.isSensitive = true + viewBinding.region14b.isSensitive = true + viewBinding.region14c.isSensitive = true + viewBinding.region14d.isSensitive = true + viewBinding.region14e.isSensitive = true + viewBinding.region14f.isSensitive = true + viewBinding.region15a.isSensitive = true + viewBinding.region15b.isSensitive = true + viewBinding.region15c.isSensitive = true + viewBinding.region15d.isSensitive = true + viewBinding.region16a.isSensitive = true + viewBinding.region16b.isSensitive = true + viewBinding.region16c.isSensitive = true + viewBinding.region16d.isSensitive = true + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotViewsFragment.kt b/app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotViewsFragment.kt new file mode 100644 index 000000000..f63dd6ce4 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/screenshot/ScreenshotViewsFragment.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.screenshot + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentScreenshotViewsBinding +import com.smartlook.app.ui.BaseFragment +import com.smartlook.app.ui.adapter.SensitiveItemAdapter +import com.smartlook.app.ui.adapter.SpinnerAdapter +import com.smartlook.app.ui.dialog.AlertDialog +import com.smartlook.app.ui.dialog.BottomSheetDialogFragment +import com.smartlook.app.ui.dialog.DialogActivity +import com.smartlook.app.ui.dialog.DialogFragment +import com.smartlook.app.ui.dialog.WindowManagerDialog +import com.smartlook.app.ui.dialog.WindowManagerToast +import com.smartlook.app.ui.wireframe.model.SimpleItem +import com.smartlook.app.util.randomColor + +class ScreenshotViewsFragment : BaseFragment() { + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentScreenshotViewsBinding + get() = FragmentScreenshotViewsBinding::inflate + + override val titleRes: Int = R.string.screenshot_fragment_views_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.dialog1.setOnClickListener(onClickListener) + viewBinding.dialog2.setOnClickListener(onClickListener) + viewBinding.dialog3.setOnClickListener(onClickListener) + viewBinding.dialog4.setOnClickListener(onClickListener) + viewBinding.dialog5.setOnClickListener(onClickListener) + viewBinding.dialog6.setOnClickListener(onClickListener) + viewBinding.sensitivityAnimation.setOnClickListener(onClickListener) + viewBinding.toast1.setOnClickListener(onClickListener) + viewBinding.toast2.setOnClickListener(onClickListener) + + val items = (0..3).map { SimpleItem(it, "Title $it", "Description of $it", randomColor()) } + val spinnerAdapter = SpinnerAdapter(requireContext(), items) + + viewBinding.spinner1.adapter = spinnerAdapter + + viewBinding.dialog1.isSensitive = true + viewBinding.textViewSensitive1.isSensitive = true + viewBinding.textViewSensitive2.isSensitive = true + viewBinding.textViewSensitive3.isSensitive = true + + viewBinding.surfaceView2.setZOrderOnTop(true) + + viewBinding.sensitiveList.adapter = SensitiveItemAdapter(requireContext(), 50) + } + + override fun onResume() { + super.onResume() + viewBinding.surfaceView1.onResume() + viewBinding.surfaceView2.onResume() + } + + override fun onPause() { + viewBinding.surfaceView1.onPause() + viewBinding.surfaceView2.onResume() + super.onPause() + } + + private fun toggleSensitiveViewVisibility() { + val isVisible = viewBinding.textViewSensitive2.visibility == View.VISIBLE + viewBinding.textViewSensitive2.visibility = if (isVisible) View.GONE else View.VISIBLE + viewBinding.cover2.visibility = if (isVisible) View.GONE else View.VISIBLE + } + + private val onClickListener = View.OnClickListener { + when (it.id) { + viewBinding.dialog1.id -> + AlertDialog.show(requireContext(), true) + viewBinding.dialog2.id -> + AlertDialog.show(requireContext(), false) + viewBinding.dialog3.id -> + DialogFragment().show(childFragmentManager, "DialogFragment") + viewBinding.dialog4.id -> + startActivity(Intent(requireContext(), DialogActivity::class.java)) + viewBinding.dialog5.id -> + BottomSheetDialogFragment().show(childFragmentManager, "BottomSheetDialogFragment") + viewBinding.dialog6.id -> + WindowManagerDialog.show(requireContext()) + viewBinding.toast1.id -> + Toast.makeText(requireContext(), "Sample Toast", Toast.LENGTH_LONG).show() + viewBinding.toast2.id -> + WindowManagerToast.show(requireContext()) + viewBinding.sensitivityAnimation.id -> + toggleSensitiveViewVisibility() + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/BridgeInterfaceFragment.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/BridgeInterfaceFragment.kt new file mode 100644 index 000000000..354287c16 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/BridgeInterfaceFragment.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe + +import android.animation.ObjectAnimator +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.LinearInterpolator +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentBridgeInterfaceBinding +import com.smartlook.app.ui.BaseFragment + +class BridgeInterfaceFragment : BaseFragment() { + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentBridgeInterfaceBinding + get() = FragmentBridgeInterfaceBinding::inflate + + override val titleRes: Int = R.string.bridge_interface_title + + private val animator by lazy { ObjectAnimator.ofFloat(viewBinding.rectangle, "rotation", 0f, 360f) } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + animator.interpolator = LinearInterpolator() + animator.repeatCount = ObjectAnimator.INFINITE + animator.repeatMode = ObjectAnimator.RESTART + animator.duration = 1000L + animator.start() + } + + override fun onDestroyView() { + super.onDestroyView() + animator.cancel() + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/CollapsingLayoutFragment.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/CollapsingLayoutFragment.kt new file mode 100644 index 000000000..53d92e8a8 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/CollapsingLayoutFragment.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentCollapsingLayoutBinding +import com.smartlook.app.ui.BaseFragment +import com.smartlook.app.ui.adapter.SensitiveItemAdapter + +class CollapsingLayoutFragment : BaseFragment() { + + override val titleRes: Int = R.string.wireframe_title + override val subtitleRes: Int = R.string.wireframe_collapsing_layout_subtitle + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentCollapsingLayoutBinding + get() = FragmentCollapsingLayoutBinding::inflate + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.recycler.adapter = SensitiveItemAdapter(requireContext(), 20) + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/EmptyActivity.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/EmptyActivity.kt new file mode 100644 index 000000000..b7af86308 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/EmptyActivity.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe + +import android.os.Bundle +import android.view.WindowManager +import androidx.fragment.app.FragmentActivity +import com.smartlook.app.R +import com.smartlook.app.ui.dialog.BottomSheetDialogFragment +import com.smartlook.sdk.common.utils.extensions.contentView + +class EmptyActivity : FragmentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_empty) + + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + contentView?.setOnClickListener { onBackPressed() } + + BottomSheetDialogFragment().show(supportFragmentManager, "BottomSheetDialogFragment") + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/ListFragment.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/ListFragment.kt new file mode 100644 index 000000000..b56927248 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/ListFragment.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import android.widget.Toast +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentWireframeListBinding +import com.smartlook.app.ui.BaseFragment + +class ListFragment : BaseFragment() { + + override val titleRes: Int = R.string.list_title + override val subtitleRes: Int? = null + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentWireframeListBinding + get() = FragmentWireframeListBinding::inflate + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val list = (0..100).map { "Title $it" } + viewBinding.list.adapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, list) + viewBinding.list.onItemClickListener = onItemClickListener + } + + private val onItemClickListener = OnItemClickListener { _, _, position, _ -> + Toast.makeText(requireContext(), "$position clicked", Toast.LENGTH_SHORT).show() + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/WebViewFragment.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/WebViewFragment.kt new file mode 100644 index 000000000..cb2d1de1a --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/WebViewFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebViewClient +import com.cisco.android.rum.integration.recording.api.extension.isSensitive +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentWebViewBinding +import com.smartlook.app.ui.BaseFragment + +@SuppressLint("SetJavaScriptEnabled") +class WebViewFragment : BaseFragment() { + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentWebViewBinding + get() = FragmentWebViewBinding::inflate + + override val titleRes: Int = R.string.web_view_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.webView1.isSensitive = true + viewBinding.webView1.webViewClient = webViewClient + viewBinding.webView1.settings.javaScriptEnabled = true + viewBinding.webView1.settings.domStorageEnabled = true + viewBinding.webView1.loadUrl("https://www.amazon.com/Oculus-Quest-Advanced-All-One-2/dp/B09P4F68WT/ref=mp_s_a_1_1_sspa?_encoding=UTF8&content-id=amzn1.sym.f2643a49-d441-49e2-8877-ba32df4e5cdc&keywords=oculus&pd_rd_r=c3ae64cb-e9b0-4894-a625-0efe81f01c0f&pd_rd_w=vzlqu&pd_rd_wg=DSoRq&pf_rd_p=f2643a49-d441-49e2-8877-ba32df4e5cdc&pf_rd_r=RBTGZK47X72SZZGVJE13&qid=1671092709&s=electronics&sr=1-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUFUMjdYNDRNRFVEM0kmZW5jcnlwdGVkSWQ9QTA0OTEwMTQxMElWU01QR1I3NDdPJmVuY3J5cHRlZEFkSWQ9QTA5NzgyNjRYUkhSM1g2VUxDV08md2lkZ2V0TmFtZT1zcF9waG9uZV9zZWFyY2hfYXRmJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ==") + + viewBinding.webView2.isSensitive = false + viewBinding.webView2.webViewClient = webViewClient + viewBinding.webView2.settings.javaScriptEnabled = true + viewBinding.webView2.settings.domStorageEnabled = true + viewBinding.webView2.loadUrl("https://www.quackit.com/html/html_editors/scratchpad/preview.cfm?example=/html/codes/html_form_code") + } + + private val webViewClient = object : WebViewClient() {} +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/WireframeViewsFragment.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/WireframeViewsFragment.kt new file mode 100644 index 000000000..a0c6938bf --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/WireframeViewsFragment.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe + +import android.content.Intent +import android.graphics.Color +import android.graphics.Matrix +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.TextPaint +import android.text.style.ClickableSpan +import android.text.style.ImageSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.text.HtmlCompat +import com.smartlook.app.R +import com.smartlook.app.databinding.FragmentWireframeViewsBinding +import com.smartlook.app.ui.BaseFragment +import com.smartlook.app.ui.adapter.SimpleItemAdapter +import com.smartlook.app.ui.adapter.SpinnerAdapter +import com.smartlook.app.ui.adapter.ViewPagerAdapter +import com.smartlook.app.ui.adapter.ViewPagerAdapter2 +import com.smartlook.app.ui.dialog.AlertDialog +import com.smartlook.app.ui.dialog.BottomSheetDialogFragment +import com.smartlook.app.ui.dialog.DialogActivity +import com.smartlook.app.ui.dialog.DialogFragment +import com.smartlook.app.ui.wireframe.model.SimpleItem +import com.smartlook.app.util.randomColor +import com.smartlook.sdk.common.utils.dpToPx + +// FIXME CalendarView is broken on Android 5.0 +class WireframeViewsFragment : BaseFragment() { + + override val titleRes: Int = R.string.wireframe_title + override val subtitleRes: Int = R.string.wireframe_native_subtitle + + override val viewBindingCreator: (LayoutInflater, ViewGroup?, Boolean) -> FragmentWireframeViewsBinding + get() = FragmentWireframeViewsBinding::inflate + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewBinding.googleMap3.onCreate(savedInstanceState) + setup() + } + + override fun onStart() { + super.onStart() + viewBinding.googleMap3.onStart() + } + + override fun onResume() { + super.onResume() + viewBinding.googleMap3.onResume() + viewBinding.surfaceView.onResume() + } + + override fun onPause() { + viewBinding.googleMap3.onPause() + viewBinding.surfaceView.onPause() + super.onPause() + } + + override fun onStop() { + viewBinding.googleMap3.onStop() + super.onStop() + } + + override fun onDestroyView() { + viewBinding.googleMap3.onDestroy() + super.onDestroyView() + } + + private fun setup() { + viewBinding.toast1.setOnClickListener(onClickListener) + + viewBinding.viewPager1.adapter = ViewPagerAdapter(childFragmentManager) + viewBinding.viewPager2.adapter = ViewPagerAdapter2(this) + + viewBinding.textInputLayout1.error = "Is it really your name?" + + val imageMatrix = Matrix() + imageMatrix.preScale(2f, 1f, 0.5f, 0.5f) + imageMatrix.preTranslate(60f, 50f) + + viewBinding.imageView11.imageMatrix = imageMatrix + viewBinding.imageView12.imageMatrix = imageMatrix + viewBinding.imageView13.imageMatrix = imageMatrix + viewBinding.imageView14.imageMatrix = imageMatrix + + val list = (0..10).map { SimpleItem(it, "Title $it", "Description of $it", randomColor()) } + val recyclerViewAdapter = SimpleItemAdapter(requireContext(), list) + + viewBinding.recyclerView1.adapter = recyclerViewAdapter + viewBinding.recyclerView2.adapter = recyclerViewAdapter + + val items = (0..3).map { SimpleItem(it, "Title $it", "Description of $it", randomColor()) } + val spinnerAdapter = SpinnerAdapter(requireContext(), items) + + viewBinding.spinner1.adapter = spinnerAdapter + viewBinding.spinner2.adapter = spinnerAdapter + + viewBinding.dialog1.setOnClickListener(onClickListener) + viewBinding.dialog2.setOnClickListener(onClickListener) + viewBinding.dialog3.setOnClickListener(onClickListener) + viewBinding.dialog4.setOnClickListener(onClickListener) + + viewBinding.textView5.setHorizontallyScrolling(true) + + viewBinding.numberPicker1.minValue = 0 + viewBinding.numberPicker1.maxValue = 10 + + viewBinding.textView6.text = HtmlCompat.fromHtml("Red text and BIG TEXT and small text and bold text and text with background and link to www.smartlook.com
  • Coffee
  • Tea
  • Milk

This text contains subscript text.
This text contains superscript text.
", HtmlCompat.FROM_HTML_MODE_COMPACT) + + viewBinding.textViewSpans.text = createTextWithSpans() + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N && Build.VERSION.SDK_INT != Build.VERSION_CODES.O) // Check was added later, null on layer causes a crash + viewBinding.viewEmptyLayer.background = LayerDrawable(arrayOf(ColorDrawable(Color.RED), null, ColorDrawable(Color.RED))) + } + + private fun createTextWithSpans(): Spannable { + val spannable = SpannableStringBuilder() + spannable.append("Intermediary bank SWIFT/BIC #") + spannable.setSpan(createIconSpan(), spannable.length - 1, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.append(" (optional)") + + val clickSpan = object : ClickableSpan() { + override fun onClick(view: View) {} + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = false + } + } + + spannable.setSpan(clickSpan, 0, spannable.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + return spannable + } + + private fun createIconSpan(): ImageSpan { + val drawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_add, requireContext().theme)?.mutate() ?: throw IllegalArgumentException() + val size = dpToPx(16f) + + drawable.setBounds(0, 0, size, size) + DrawableCompat.setTint(drawable, Color.RED) + + return ImageSpan(drawable) + } + + private val onClickListener = View.OnClickListener { + when (it.id) { + viewBinding.toast1.id -> + Toast.makeText(requireContext(), "Sample Toast", Toast.LENGTH_LONG).show() + viewBinding.dialog1.id -> + AlertDialog.show(requireContext()) + viewBinding.dialog2.id -> + DialogFragment().show(childFragmentManager, "DialogFragment") + viewBinding.dialog3.id -> + startActivity(Intent(requireContext(), DialogActivity::class.java)) + viewBinding.dialog4.id -> + BottomSheetDialogFragment().show(childFragmentManager, "BottomSheetDialogFragment") + } + } +} diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyGoogleMapFragment.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyGoogleMapFragment.kt new file mode 100644 index 000000000..439c4c42a --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyGoogleMapFragment.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe.extend + +import com.google.android.gms.maps.SupportMapFragment + +class MyGoogleMapFragment : SupportMapFragment() diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyTextView.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyTextView.kt new file mode 100644 index 000000000..28ab64f03 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/extend/MyTextView.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe.extend + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatTextView + +class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) diff --git a/app/src/main/java/com/smartlook/app/ui/wireframe/model/SimpleItem.kt b/app/src/main/java/com/smartlook/app/ui/wireframe/model/SimpleItem.kt new file mode 100644 index 000000000..07d0974dd --- /dev/null +++ b/app/src/main/java/com/smartlook/app/ui/wireframe/model/SimpleItem.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.ui.wireframe.model + +import android.graphics.Color + +data class SimpleItem( + val id: Int, + val title: String, + val description: String, + val backgroundColor: Int = Color.TRANSPARENT +) diff --git a/app/src/main/java/com/smartlook/app/util/Common.kt b/app/src/main/java/com/smartlook/app/util/Common.kt new file mode 100644 index 000000000..e68256239 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/util/Common.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.util + +import android.graphics.Color + +fun randomColor(): Int { + val hue = Math.random().toFloat() * 360f + val saturation = (Math.random().toFloat() * 2000f + 1000f) / 10000f + val luminance = 0.9f + return Color.HSVToColor(floatArrayOf(hue, saturation, luminance)) +} diff --git a/app/src/main/java/com/smartlook/app/util/FragmentAnimation.kt b/app/src/main/java/com/smartlook/app/util/FragmentAnimation.kt new file mode 100644 index 000000000..e20988806 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/util/FragmentAnimation.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.util + +import androidx.annotation.AnimRes +import androidx.annotation.AnimatorRes +import com.smartlook.app.R + +data class FragmentAnimation( + @AnimatorRes @AnimRes val enter: Int, + @AnimatorRes @AnimRes val exit: Int, + @AnimatorRes @AnimRes val popEnter: Int = 0, + @AnimatorRes @AnimRes val popExit: Int = 0 +) { + + companion object { + val FADE = FragmentAnimation(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) + val SLOW_FADE = FragmentAnimation(R.animator.slow_fade_in, R.animator.slow_fade_out, R.animator.slow_fade_in, R.animator.slow_fade_out) + val SLOW_FADE_1 = FragmentAnimation(0, R.animator.slow_fade_out, 0, R.animator.slow_fade_out) + val SLOW_FADE_2 = FragmentAnimation(R.animator.slow_fade_in, 0, R.animator.slow_fade_in, 0) + val SLOW_TRANSLATE = FragmentAnimation(R.anim.slow_enter_from_right, R.anim.slow_exit_to_left, R.anim.slow_enter_from_left, R.anim.slow_exit_to_right) + val SLOW_TRANSLATE_1 = FragmentAnimation(0, R.anim.slow_exit_to_left, 0, R.anim.slow_exit_to_right) + val SLOW_TRANSLATE_2 = FragmentAnimation(R.anim.slow_enter_from_right, 0, R.anim.slow_enter_from_left, 0) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/BarChartView.kt b/app/src/main/java/com/smartlook/app/view/BarChartView.kt new file mode 100644 index 000000000..6b1c31a00 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/BarChartView.kt @@ -0,0 +1,281 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.os.Build +import android.util.AttributeSet +import android.view.View +import com.smartlook.sdk.common.utils.dpToPxF +import kotlin.math.max + +class BarChartView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint() + + init { + paint.textSize = LABEL_SIZE + } + + override fun onDraw(canvas: Canvas) { + val legendLeft = drawLegend(canvas) + val axisRight = drawAxis(canvas, legendLeft) + drawChart(canvas, axisRight, legendLeft) + } + + @Suppress("UnnecessaryVariable") + private fun drawAxis(canvas: Canvas, legendLeft: Float): Float { + val textHeight = paint.descent() - paint.ascent() + + val min = 0f + val minLabel = min.toInt().toString() + val minWidth = paint.measureText(minLabel) + + val max = ENTRIES.maxOf { it.values.maxOf { it.y } } + val maxLabel = max.toInt().toString() + val maxWidth = paint.measureText(maxLabel) + + val labelWidth = max(minWidth, maxWidth) + + paint.color = Color.BLACK + paint.textAlign = Paint.Align.RIGHT + canvas.drawText(minLabel, labelWidth, height - paddingBottom - CHART_VERTICAL_MARGIN - textHeight, paint) + canvas.drawText(maxLabel, labelWidth, -paint.ascent(), paint) + + val verticalAxisLeft = labelWidth + LABEL_MARGIN + val verticalAxisTop = paddingTop.toFloat() + val verticalAxisRight = verticalAxisLeft + AXIS_LINE_WIDTH + val verticalAxisBottom = height - paddingBottom - CHART_VERTICAL_MARGIN - textHeight + + paint.color = Color.LTGRAY + canvas.drawRect(verticalAxisLeft, verticalAxisTop, verticalAxisRight, verticalAxisBottom, paint) + + val horizontalAxisLeft = verticalAxisRight + val horizontalAxisTop = verticalAxisBottom - AXIS_LINE_WIDTH + val horizontalAxisRight = legendLeft - CHART_HORIZONTAL_MARGIN + val horizontalAxisBottom = verticalAxisBottom + + canvas.drawRect(horizontalAxisLeft, horizontalAxisTop, horizontalAxisRight, horizontalAxisBottom, paint) + + return verticalAxisRight + } + + private fun drawChart(canvas: Canvas, axisRight: Float, legendLeft: Float) { + val sections = ENTRIES.toSections() + val bars = sections.toBars() + + val textHeight = paint.descent() - paint.ascent() + + val contentBottom = height - paddingBottom - CHART_VERTICAL_MARGIN - textHeight + val contentWidth = legendLeft - axisRight - CHART_HORIZONTAL_MARGIN + val contentHeight = height - paddingTop - paddingBottom + + val max = bars.maxOf { (it as? Bar.Colored)?.value ?: 0f } + val barWidth = contentWidth / bars.size + + var sectionLeft: Float + var sectionRight = axisRight + + val labelY = height - paddingBottom - paint.descent() + + paint.textAlign = Paint.Align.CENTER + paint.color = Color.BLACK + + for (i in sections.indices) { + val section = sections[i] + + sectionLeft = sectionRight + sectionRight = sectionLeft + section.values.size * barWidth + + val labelX = sectionLeft + (sectionRight - sectionLeft) / 2f + i * barWidth + + canvas.drawText(section.x.toInt().toString(), labelX, labelY, paint) + } + + for (i in bars.indices) { + val bar = bars[i] + + if (bar is Bar.Colored) { + val left = axisRight + i * barWidth + val right = left + barWidth + val top = paddingTop + contentHeight - (contentHeight * bar.value / max) + + paint.color = bar.color + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + canvas.drawRoundRect(left, top, right, contentBottom, CHART_CORNER_RADIUS, CHART_CORNER_RADIUS, paint) + else + canvas.drawRect(left, top, right, contentBottom, paint) + } + } + } + + private fun drawLegend(canvas: Canvas): Float { + paint.textAlign = Paint.Align.LEFT + + val maxWidth = ENTRIES.maxOf { paint.measureText(it.label) } + val textHeight = paint.descent() - paint.ascent() + + val legendHeight = ENTRIES.size * textHeight + (ENTRIES.size - 1) * LABEL_MARGIN + val contentHeight = height - paddingTop - paddingBottom + val legendTop = paddingTop + (contentHeight - legendHeight) / 2 + val legendEnd = paddingStart + width - paddingStart - paddingEnd + + for (i in ENTRIES.indices) { + val entry = ENTRIES[i] + val top = legendTop + i * (textHeight + LABEL_MARGIN) + + val labelX = legendEnd - maxWidth + val labelY = top - paint.ascent() + + paint.color = Color.BLACK + canvas.drawText(entry.label, labelX, labelY, paint) + + val rectRight = labelX - LABEL_MARGIN + val rectLeft = rectRight - textHeight * LABEL_POINT_SIZE_RATIO + val rectTop = top + textHeight * (1f - LABEL_POINT_SIZE_RATIO) / 2f + val rectBottom = rectTop + textHeight * LABEL_POINT_SIZE_RATIO + + paint.color = entry.color + canvas.drawRect(rectLeft, rectTop, rectRight, rectBottom, paint) + } + + return legendEnd - (maxWidth + LABEL_MARGIN + textHeight * LABEL_POINT_SIZE_RATIO) + } + + private fun List.toSections(): List
{ + val sections = HashMap>() + + for (entry in this) + for (value in entry.values) { + val values = sections[value.x] ?: ArrayList() + values += Section.Value(value.y, entry.color) + sections[value.x] = values + } + + return sections.map { Section(it.key, it.value) } + } + + private fun List
.toBars(): List { + val bars = ArrayList() + + for (section in this) { + for (value in section.values) + bars += Bar.Colored(value.value, value.color) + + bars += Bar.Empty() + } + + bars.removeLast() + return bars + } + + private data class Entry( + val label: String, + val color: Int, + val values: List + ) { + + data class Value( + val x: Float, + val y: Float + ) + } + + private data class Section( + val x: Float, + val values: List + ) { + + data class Value( + val value: Float, + val color: Int + ) + } + + @Suppress("CanSealedSubClassBeObject") + private sealed class Bar { + + class Empty : Bar() + + data class Colored( + val value: Float, + val color: Int + ) : Bar() + } + + companion object { + + private val LABEL_SIZE = dpToPxF(10f) + private val LABEL_MARGIN = dpToPxF(5f) + + private val CHART_HORIZONTAL_MARGIN = dpToPxF(15f) + private val CHART_VERTICAL_MARGIN = dpToPxF(5f) + + private val CHART_CORNER_RADIUS = dpToPxF(5f) + + private val AXIS_LINE_WIDTH = dpToPxF(1f) + + private const val LABEL_POINT_SIZE_RATIO = 0.5f + + private val ENTRIES = listOf( + Entry( + "Apple", 0xff8fb9aa.toInt(), + listOf( + Entry.Value(2012f, 25f), + Entry.Value(2013f, 23f), + Entry.Value(2014f, 27f), + ) + ), + Entry( + "Banana", 0xfff2d096.toInt(), + listOf( + Entry.Value(2012f, 30f), + Entry.Value(2013f, 32f), + Entry.Value(2014f, 35f), + ) + ), + Entry( + "Orange", 0xffb2e7e8.toInt(), + listOf( + Entry.Value(2012f, 35f), + Entry.Value(2013f, 32f), + Entry.Value(2014f, 33f), + ) + ), + Entry( + "Blueberry", 0xff304d63.toInt(), + listOf( + Entry.Value(2012f, 50f), + Entry.Value(2013f, 45f), + Entry.Value(2014f, 55f), + ) + ), + Entry( + "Lemon", 0xffed8975.toInt(), + listOf( + Entry.Value(2012f, 42f), + Entry.Value(2013f, 43f), + Entry.Value(2014f, 45f), + ) + ) + ) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/BrokenSeekBar.kt b/app/src/main/java/com/smartlook/app/view/BrokenSeekBar.kt new file mode 100644 index 000000000..48cf7910a --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/BrokenSeekBar.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View + +class BrokenSeekBar(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + + private var fraction = 0f + + override fun onDraw(canvas: Canvas) { + paint.color = Color.GRAY + canvas.drawRect(paddingLeft.toFloat(), paddingTop.toFloat(), (width - paddingRight).toFloat(), (height - paddingBottom).toFloat(), paint) + + val handrailSize = (height - paddingTop - paddingBottom) + val left = paddingLeft + fraction * (width - paddingLeft - paddingRight - handrailSize) + + paint.color = Color.RED + canvas.drawRect(left, paddingTop.toFloat(), left + handrailSize, (height - paddingBottom).toFloat(), paint) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + val handrailSize = (height - paddingTop - paddingBottom) + fraction = ((event.x - paddingLeft - handrailSize / 2f) / (width - paddingLeft - paddingRight - handrailSize)).coerceIn(0f, 1f) + + if (!IS_BROKEN) + when (event.action) { + MotionEvent.ACTION_DOWN -> + parent.requestDisallowInterceptTouchEvent(true) + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> + parent.requestDisallowInterceptTouchEvent(false) + } + + invalidate() + return true + } + + private companion object { + const val IS_BROKEN = false + } +} diff --git a/app/src/main/java/com/smartlook/app/view/CanvasElementsView.kt b/app/src/main/java/com/smartlook/app/view/CanvasElementsView.kt new file mode 100644 index 000000000..f688b86ee --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/CanvasElementsView.kt @@ -0,0 +1,383 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.CornerPathEffect +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Shader +import android.util.AttributeSet +import android.view.Choreographer +import android.view.View +import com.smartlook.app.R +import com.smartlook.app.extension.add +import com.smartlook.app.extension.rotate +import com.smartlook.sdk.common.utils.dpToPxF +import kotlin.math.ceil +import kotlin.math.min + +class CanvasElementsView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val choreographer = Choreographer.getInstance() + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val bounds = RectF() + + private val elements = ArrayList() + + init { + elements += FixedArcSweepElement(false) + elements += FixedArcSweepElement(true) + elements += VariableArcSweepAndAngleElement(false) + elements += VariableArcSweepAndAngleElement(true) + elements += DrawPathElement() + elements += ColorElement() + elements += TextElement() + elements += RgbElement() + elements += DrawPaintElement() + elements += ScaledRectElement() + elements += ScaledRectPivotElement() + elements += ScaledCircleElement() + elements += BitmapElement() + elements += PathLineElement() + elements += RectOutlineElement() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + choreographer.postFrameCallback(frameCallback) + } + + override fun onDetachedFromWindow() { + choreographer.removeFrameCallback(frameCallback) + super.onDetachedFromWindow() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val width = measuredWidth - paddingLeft - paddingRight + val itemsPerLine = ((width + ELEMENT_MARGIN) / (ELEMENT_SIZE + ELEMENT_MARGIN)).toInt() + val height = (ceil(elements.size / itemsPerLine.toFloat()) * (ELEMENT_SIZE + ELEMENT_MARGIN) - ELEMENT_MARGIN + 0.5f).toInt() + + setMeasuredDimension(measuredWidth, height) + } + + override fun onDraw(canvas: Canvas) { + val left = paddingLeft.toFloat() + val top = paddingTop.toFloat() + val right = (width - paddingRight).toFloat() + val bottom = (height - paddingBottom).toFloat() + + var x = left + var y = top + + for (element in elements) { + if (x + ELEMENT_SIZE > right) { + x = left + y += ELEMENT_SIZE + ELEMENT_MARGIN + + if ((y + ELEMENT_SIZE).toInt() > bottom) + break + } + + bounds.set(x, y, x + ELEMENT_SIZE, y + ELEMENT_SIZE) + element.onDraw(canvas, paint, bounds) + + x += ELEMENT_SIZE + ELEMENT_MARGIN + } + } + + private val frameCallback = object : Choreographer.FrameCallback { + + private val viewRect = Rect() + private val rootRect = Rect() + + override fun doFrame(frameTimeNanos: Long) { + choreographer.postFrameCallback(this) + + if (visibility != VISIBLE) + return + + getGlobalVisibleRect(viewRect) + rootView.getGlobalVisibleRect(rootRect) + + if (!Rect.intersects(viewRect, rootRect)) + return + + invalidate() + } + } + + private interface Element { + fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) + } + + private class FixedArcSweepElement(private val useCenter: Boolean) : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + val angle = (System.currentTimeMillis() / 10 % 360).toFloat() + + val space = bounds.height() * 0.2f + bounds.top += space + bounds.bottom -= space + + paint.color = 0xff8fb9aa.toInt() + canvas.drawArc(bounds, angle, 120f, useCenter, paint) + } + } + + private class VariableArcSweepAndAngleElement(private val useCenter: Boolean) : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + val anim = System.currentTimeMillis() / 5 + val angle = (System.currentTimeMillis() / 50 % 360).toFloat() + val phase = (anim % 720).toFloat() + var sweep = (anim % 360).toFloat() + + if (phase > 360f) + sweep = 360 - sweep + + val space = bounds.width() * 0.2f + bounds.left += space + bounds.right -= space + + paint.color = 0xfff2d096.toInt() + canvas.drawArc(bounds, angle, sweep, useCenter, paint) + } + } + + private class DrawPathElement : Element { + + private val path = Path() + + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + val width = bounds.width() + val height = bounds.height() + + val left = width * 0.4f / 2f + val top = height * 0.3f / 2f + val right = left + width * 0.6f + val bottom = top + height * 0.7f + + val angle = (System.currentTimeMillis() / 15 % 360).toFloat() + val pivot = PointF(width / 2f, height / 2f) + + val a = PointF(left, top).rotate(angle, pivot).add(bounds.left, bounds.top) + val b = PointF(left, bottom).rotate(angle, pivot).add(bounds.left, bounds.top) + val c = PointF(right, bottom).rotate(angle, pivot).add(bounds.left, bounds.top) + val d = PointF(right, top).rotate(angle, pivot).add(bounds.left, bounds.top) + + path.reset() + path.moveTo(a.x, a.y) + path.lineTo(b.x, b.y) + path.lineTo(c.x, c.y) + path.lineTo(d.x, d.y) + path.close() + + paint.color = 0xffb2e7e8.toInt() + canvas.drawPath(path, paint) + } + } + + private class ColorElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + canvas.save() + canvas.clipRect(bounds) + canvas.drawColor(0xff304d63.toInt()) + canvas.restore() + } + } + + private class TextElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + paint.textAlign = Paint.Align.CENTER + paint.textSize = dpToPxF(12f) + paint.color = 0xff304d63.toInt() + + val x = bounds.left + bounds.width() / 2f + val y = bounds.top + bounds.height() / 2f - (paint.descent() - paint.ascent()) / 2f - paint.ascent() + + canvas.drawText("Text", x, y, paint) + } + } + + private class RgbElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + canvas.save() + canvas.clipRect(bounds) + canvas.drawRGB(200, 150, 60) + canvas.restore() + } + } + + private class DrawPaintElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + canvas.save() + canvas.clipRect(bounds) + paint.shader = LinearGradient(bounds.left, bounds.top, bounds.right, bounds.bottom, 0xff304d63.toInt(), 0xffed8975.toInt(), Shader.TileMode.REPEAT) + canvas.drawPaint(paint) + paint.shader = null + canvas.restore() + } + } + + private class ScaledRectElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + paint.color = 0xff304d63.toInt() + canvas.drawRect(bounds, paint) + + canvas.save() + canvas.scale(0.5f, 0.5f) + + val width = bounds.width() + val height = bounds.height() + + bounds.left *= 2f + bounds.top *= 2f + bounds.right = bounds.left + width + bounds.bottom = bounds.top + height + + paint.color = 0xffed8975.toInt() + canvas.translate(width / 2f, height / 2f) + canvas.drawRect(bounds, paint) + + canvas.restore() + } + } + + private class ScaledRectPivotElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + val width = bounds.width() + val height = bounds.height() + + paint.color = 0xff8fb9aa.toInt() + canvas.drawRect(bounds, paint) + + canvas.save() + canvas.scale(0.5f, 0.5f, width, height) + + bounds.left *= 2f + bounds.top *= 2f + bounds.right = bounds.left + width + bounds.bottom = bounds.top + height + + paint.color = 0xfff2d096.toInt() + canvas.translate(-width / 2f, -height / 2f) + canvas.drawRect(bounds, paint) + canvas.restore() + } + } + + private class ScaledCircleElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + val width = bounds.width() + val height = bounds.height() + + val cx = bounds.left + width / 2f + val cy = bounds.top + height / 2f + val radius = min(width, height) / 2f + + paint.color = 0xffed8975.toInt() + canvas.drawCircle(cx, cy, radius, paint) + + canvas.save() + canvas.scale(2f, 2f) + canvas.scale(0.25f, 0.25f) + + paint.color = 0xff304d63.toInt() + canvas.drawCircle(cx * 2f, cy * 2f, radius, paint) + + canvas.restore() + } + } + + private inner class BitmapElement : Element { + + private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_bitmap_sample_5) + + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + canvas.save() + canvas.clipRect(bounds) + canvas.drawBitmap(bitmap, null, bounds, null) + canvas.restore() + } + } + + private class PathLineElement : Element { + + private val strokeWidth = dpToPxF(5f) + private val cornerPathEffect = CornerPathEffect(strokeWidth) + private val points = arrayOf(PointF(0f, 0.5f), PointF(0.1f, 0.5f), PointF(0.33f, 0.2f), PointF(0.66f, 0.8f), PointF(0.9f, 0.5f), PointF(1f, 0.5f)) + private val shader = LinearGradient(0f, 0f, ELEMENT_SIZE, ELEMENT_SIZE, intArrayOf(0xff8fb9aa.toInt(), 0xffed8975.toInt()), floatArrayOf(0f, 1f), Shader.TileMode.MIRROR) + private val path = Path() + + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + val width = bounds.width() + val height = bounds.height() + + path.reset() + path.moveTo(bounds.left + points[0].x * width, bounds.top + points[0].y * height) + + for (i in 1 until points.size) + path.lineTo(bounds.left + points[i].x * width, bounds.top + points[i].y * height) + + paint.color = 0xff000000.toInt() + paint.style = Paint.Style.STROKE + paint.strokeWidth = strokeWidth + paint.shader = shader + paint.strokeCap = Paint.Cap.ROUND + paint.pathEffect = cornerPathEffect + + canvas.drawPath(path, paint) + + paint.style = Paint.Style.FILL + paint.shader = null + paint.strokeCap = Paint.Cap.BUTT + paint.pathEffect = null + } + } + + private class RectOutlineElement : Element { + override fun onDraw(canvas: Canvas, paint: Paint, bounds: RectF) { + paint.color = 0xff304d63.toInt() + paint.style = Paint.Style.STROKE + paint.strokeWidth = dpToPxF(10f) + + val width = bounds.width() + val height = bounds.height() + + bounds.left += width * 0.3f + bounds.top += height * 0.3f + bounds.right -= width * 0.3f + bounds.bottom -= height * 0.3f + + canvas.drawRect(bounds, paint) + + paint.style = Paint.Style.FILL + } + } + + companion object { + private val ELEMENT_MARGIN = dpToPxF(10f) + private val ELEMENT_SIZE = dpToPxF(50f) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/CanvasTextureView.kt b/app/src/main/java/com/smartlook/app/view/CanvasTextureView.kt new file mode 100644 index 000000000..1c24ac4b8 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/CanvasTextureView.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Path +import android.graphics.SurfaceTexture +import android.os.SystemClock +import android.util.AttributeSet +import android.view.Surface +import android.view.TextureView + +// FIXME Animations + +class CanvasTextureView(context: Context, attrs: AttributeSet? = null) : TextureView(context, attrs) { + + init { + surfaceTextureListener = MySurfaceTextureListener() + } + + private inner class MySurfaceTextureListener : SurfaceTextureListener { + + private var surface: Surface? = null + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + val localSurface = Surface(surface) + this.surface = localSurface + + draw(localSurface, width, height) + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + return true + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {} + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {} + + private fun draw(surface: Surface, width: Int, height: Int) { + val canvas = surface.lockCanvas(null) + val paint = Paint() + val path = Path() + + canvas.drawColor(0xffcccccc.toInt()) + + paint.color = Color.BLUE + canvas.drawRect(width * 0.25f, height * 0.25f, width * 0.75f, height * 0.75f, paint) + + path.moveTo(width * 0.5f, height * 0.2f) + path.lineTo(width * 0.15f, height * 0.65f) + path.lineTo(width * 0.85f, height * 0.65f) + path.close() + paint.color = Color.RED + + canvas.save() + canvas.rotate(-0.09f * SystemClock.uptimeMillis() % 4000L, width / 2f, height / 2f) + canvas.drawPath(path, paint) + canvas.restore() + + surface.unlockCanvasAndPost(canvas) + } + } +} diff --git a/app/src/main/java/com/smartlook/app/view/ColoredLinearLayout.kt b/app/src/main/java/com/smartlook/app/view/ColoredLinearLayout.kt new file mode 100644 index 000000000..164a1410d --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/ColoredLinearLayout.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import com.smartlook.app.R + +class ColoredLinearLayout(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { + + init { + if (attrs != null) { + val a = context.obtainStyledAttributes(attrs, R.styleable.ColoredLinearLayout) + val count = a.getInt(R.styleable.ColoredLinearLayout_colored_itemsCount, 0) + val itemWidth = a.getDimension(R.styleable.ColoredLinearLayout_colored_itemWidth, 0f) + val itemHeight = a.getDimension(R.styleable.ColoredLinearLayout_colored_itemHeight, 0f) + a.recycle() + + createItems(count, itemWidth.toInt(), itemHeight.toInt()) + } + } + + private fun createItems(count: Int, width: Int, height: Int) { + for (i in 0 until count) { + val view = View(context) + view.setBackgroundColor(COLORS[i % COLORS.size]) + addView(view, LayoutParams(width, height)) + } + } + + companion object { + private val COLORS = intArrayOf(0xffff0000.toInt(), 0xff00ff00.toInt(), 0xff0000ff.toInt()) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/HardwareBitmapView.kt b/app/src/main/java/com/smartlook/app/view/HardwareBitmapView.kt new file mode 100644 index 000000000..5b17be5bb --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/HardwareBitmapView.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.ImageDecoder +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.AttributeSet +import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatImageView +import com.smartlook.app.R +import com.smartlook.sdk.common.utils.dpToPxF + +class HardwareBitmapView(context: Context, attrs: AttributeSet? = null) : AppCompatImageView(context, attrs) { + + private val paint = Paint() + private val rect = Rect() + private val bitmap: Bitmap? + + init { + bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !isInEditMode) { + val source = ImageDecoder.createSource(resources, R.drawable.ic_bitmap_big) + ImageDecoder.decodeBitmap(source, OnHeaderDecodedListener()) + } else + null + + if (bitmap != null) { + background = HwBitmapDrawable(bitmap) + setImageBitmap(bitmap) + } + + paint.textAlign = Paint.Align.CENTER + paint.textSize = dpToPxF(15f) + paint.color = Color.RED + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (bitmap != null) { + rect.right = width + rect.bottom = height + canvas.drawBitmap(bitmap, null, rect, null) + } else + canvas.drawText("Android 9+", width / 2f, (height + paint.descent() - paint.ascent()) / 2f, paint) + } + + @RequiresApi(Build.VERSION_CODES.P) + private class OnHeaderDecodedListener : ImageDecoder.OnHeaderDecodedListener { + override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageDecoder.ImageInfo, source: ImageDecoder.Source) { + decoder.allocator = ImageDecoder.ALLOCATOR_HARDWARE + } + } + + private class HwBitmapDrawable(private val bitmap: Bitmap) : Drawable() { + + private val dstRect = Rect() + + override fun draw(canvas: Canvas) { + dstRect.right = intrinsicWidth + dstRect.bottom = intrinsicHeight + + canvas.drawBitmap(bitmap, null, dstRect, null) + } + + override fun setAlpha(alpha: Int) {} + + override fun setColorFilter(colorFilter: ColorFilter?) {} + + @Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.TRANSPARENT", "android.graphics.PixelFormat")) + override fun getOpacity(): Int { + return PixelFormat.TRANSPARENT + } + } +} diff --git a/app/src/main/java/com/smartlook/app/view/RuntimeShaderView.kt b/app/src/main/java/com/smartlook/app/view/RuntimeShaderView.kt new file mode 100644 index 000000000..5ed5ce316 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/RuntimeShaderView.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RuntimeShader +import android.os.Build +import android.util.AttributeSet +import android.view.View +import androidx.annotation.RequiresApi +import com.smartlook.sdk.common.utils.dpToPxF + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +class RuntimeShaderView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint() + + init { + paint.textAlign = Paint.Align.CENTER + paint.textSize = dpToPxF(15f) + paint.color = Color.RED + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (paint.shader == null) { + val shader = RuntimeShader(SHADER_PROGRAM) + + shader.setColorUniform("startColor", Color.BLUE) + shader.setColorUniform("endColor", Color.RED) + shader.setFloatUniform("size", width.toFloat(), height.toFloat()) + + paint.shader = shader + } + + canvas.drawPaint(paint) + } else + canvas.drawText("Android 13+", width / 2f, (height + paint.descent() - paint.ascent()) / 2f, paint) + } + + private companion object { + const val SHADER_PROGRAM = """ +layout(color) uniform vec4 startColor; +layout(color) uniform vec4 endColor; + +uniform vec2 size; + +vec4 main(vec2 canvas_coordinates) { + vec2 coors = canvas_coordinates / size; + return mix(startColor, endColor, coors.x); +} + """ + } +} diff --git a/app/src/main/java/com/smartlook/app/view/SequenceView.kt b/app/src/main/java/com/smartlook/app/view/SequenceView.kt new file mode 100644 index 000000000..6e3cbe7e9 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/SequenceView.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.smartlook.sdk.common.utils.dpToPxF + +class SequenceView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint() + + private var isMeasured = false + + private var wasDrawnInLayout = false + private var wasDrawnBeforeAttach = false + private var wasDrawnBeforeMeasure = false + + init { + paint.textAlign = Paint.Align.CENTER + paint.textSize = dpToPxF(15f) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + post { requestLayout() } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + isMeasured = true + } + + override fun onDraw(canvas: Canvas) { + if (isLayoutRequested) + wasDrawnInLayout = true + + if (!isAttachedToWindow) + wasDrawnBeforeAttach = true + + if (!isMeasured) + wasDrawnBeforeMeasure = true + + val x = width / 2f + val y = (height - paint.ascent()) / 2f + + if (wasDrawnInLayout) { + canvas.drawColor(Color.RED) + canvas.drawText("Draw when layout is requested", x, y, paint) + return + } + + if (wasDrawnBeforeAttach) { + canvas.drawColor(Color.RED) + canvas.drawText("Draw before attach", x, y, paint) + return + } + + if (wasDrawnBeforeMeasure) { + canvas.drawColor(Color.RED) + canvas.drawText("Draw before measure", x, y, paint) + return + } + + canvas.drawColor(Color.GREEN) + canvas.drawText("Correct", x, y, paint) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/ShadowView.kt b/app/src/main/java/com/smartlook/app/view/ShadowView.kt new file mode 100644 index 000000000..b4148017c --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/ShadowView.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.smartlook.app.R +import com.smartlook.sdk.common.utils.dpToPxF + +class ShadowView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + + private var shadowColor = Color.GRAY + private var fillColor = Color.WHITE + private var textColor = Color.BLACK + + init { + if (attrs != null) { + val a = context.obtainStyledAttributes(attrs, R.styleable.ShadowView) + fillColor = a.getColor(R.styleable.ShadowView_shadow_fillColor, fillColor) + shadowColor = a.getColor(R.styleable.ShadowView_shadow_shadowColor, shadowColor) + a.recycle() + } + + paint.setShadowLayer(dpToPxF(10f), dpToPxF(1f), dpToPxF(5f), shadowColor) + paint.textAlign = Paint.Align.CENTER + paint.textSize = dpToPxF(15f) + + textColor = if ((Color.red(fillColor) * 299 + Color.green(fillColor) * 587 + Color.blue(fillColor) * 114) / 1000 >= 128) Color.BLACK else Color.WHITE + } + + override fun onDraw(canvas: Canvas) { + paint.color = fillColor + canvas.drawRoundRect(paddingLeft.toFloat(), paddingTop.toFloat(), (width - paddingRight).toFloat(), (height - paddingBottom).toFloat(), CORNER_RADIUS, CORNER_RADIUS, paint) + + paint.color = textColor + canvas.drawText("Shadow", paddingLeft + (width - paddingLeft - paddingRight) / 2f, paddingTop + (height - paddingTop - paddingBottom) / 2f + paint.descent(), paint) + } + + companion object { + private val CORNER_RADIUS = dpToPxF(10f) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/SimpleCanvasView.kt b/app/src/main/java/com/smartlook/app/view/SimpleCanvasView.kt new file mode 100644 index 000000000..8723028cf --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/SimpleCanvasView.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View + +class SimpleCanvasView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint() + + override fun onDraw(canvas: Canvas) { + paint.color = Color.RED + canvas.drawRect(0f, 0f, width.toFloat(), height * 0.3f, paint) + + paint.color = Color.BLUE + canvas.drawRect(0f, height * 0.3f, width.toFloat(), height.toFloat(), paint) + + paint.color = Color.GREEN + canvas.drawRect(0.3f * width, 0f, 0.5f * width, height.toFloat(), paint) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/SimpleGLSurfaceView.kt b/app/src/main/java/com/smartlook/app/view/SimpleGLSurfaceView.kt new file mode 100644 index 000000000..0083b3c2f --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/SimpleGLSurfaceView.kt @@ -0,0 +1,304 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.opengl.GLES20 +import android.opengl.GLSurfaceView +import android.opengl.Matrix +import android.os.SystemClock +import android.util.AttributeSet +import android.util.Log +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer +import java.nio.ShortBuffer +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.opengles.GL10 + +// FIXME Animations + +// https://android.googlesource.com/platform/development/+/master/samples/OpenGL/HelloOpenGLES20/src/com/example/android/opengl +class SimpleGLSurfaceView(context: Context, attrs: AttributeSet? = null) : GLSurfaceView(context, attrs) { + + init { + setEGLContextClientVersion(2) + setRenderer(MyGLRenderer()) + renderMode = RENDERMODE_CONTINUOUSLY + } + + private class MyGLRenderer : Renderer { + private lateinit var mTriangle: Triangle + private lateinit var mSquare: Square + + private val mMVPMatrix = FloatArray(16) + private val mProjectionMatrix = FloatArray(16) + private val mViewMatrix = FloatArray(16) + private val mRotationMatrix = FloatArray(16) + + override fun onSurfaceCreated(unused: GL10, config: EGLConfig) { + // Set the background frame color + GLES20.glClearColor(0.8f, 0.8f, 0.8f, 1.0f) + mTriangle = Triangle() + mSquare = Square() + } + + override fun onDrawFrame(unused: GL10) { + val scratch = FloatArray(16) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT) + Matrix.setLookAtM(mViewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0f, 1.0f, 0.0f) + + Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0) + mSquare.draw(mMVPMatrix) + + val angle = 0.09f * SystemClock.uptimeMillis() % 4000L + Matrix.setRotateM(mRotationMatrix, 0, angle, 0f, 0f, 1.0f) + Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0) + mTriangle.draw(scratch) + } + + override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) { + // Adjust the viewport based on geometry changes, + // such as screen rotation + GLES20.glViewport(0, 0, width, height) + val ratio = width.toFloat() / height + // this projection matrix is applied to object coordinates + // in the onDrawFrame() method + Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f) + } + + companion object { + private const val TAG = "MyGLRenderer" + + fun loadShader(type: Int, shaderCode: String?): Int { + // create a vertex shader type (GLES20.GL_VERTEX_SHADER) + // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) + val shader = GLES20.glCreateShader(type) + // add the source code to the shader and compile it + GLES20.glShaderSource(shader, shaderCode) + GLES20.glCompileShader(shader) + return shader + } + + fun checkGlError(glOperation: String) { + var error: Int + while (GLES20.glGetError().also { error = it } != GLES20.GL_NO_ERROR) { + Log.e(TAG, "$glOperation: glError $error") + throw java.lang.RuntimeException("$glOperation: glError $error") + } + } + } + } + + private class Square { + + // number of coordinates per vertex in this array + val COORDS_PER_VERTEX = 3 + var squareCoords = floatArrayOf( + -0.5f, 0.5f, 0.0f, // top left + -0.5f, -0.5f, 0.0f, // bottom left + 0.5f, -0.5f, 0.0f, // bottom right + 0.5f, 0.5f, 0.0f + ) // top right + + private val vertexShaderCode = // This matrix member variable provides a hook to manipulate + // the coordinates of the objects that use this vertex shader + "uniform mat4 uMVPMatrix;" + + "attribute vec4 vPosition;" + + "void main() {" + // The matrix must be included as a modifier of gl_Position. + // Note that the uMVPMatrix factor *must be first* in order + // for the matrix multiplication product to be correct. + " gl_Position = uMVPMatrix * vPosition;" + + "}" + + private val fragmentShaderCode = ( + "precision mediump float;" + + "uniform vec4 vColor;" + + "void main() {" + + " gl_FragColor = vColor;" + + "}" + ) + + private val vertexBuffer: FloatBuffer + private val drawListBuffer: ShortBuffer + private val mProgram: Int + private var mPositionHandle = 0 + private var mColorHandle = 0 + private var mMVPMatrixHandle = 0 + private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw vertices + private val vertexStride = COORDS_PER_VERTEX * 4 // 4 bytes per vertex + var color = floatArrayOf(0f, 0f, 1f, 1f) + + fun draw(mvpMatrix: FloatArray?) { + // Add program to OpenGL environment + GLES20.glUseProgram(mProgram) + // get handle to vertex shader's vPosition member + mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition") + // Enable a handle to the triangle vertices + GLES20.glEnableVertexAttribArray(mPositionHandle) + // Prepare the triangle coordinate data + GLES20.glVertexAttribPointer( + mPositionHandle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, + vertexStride, vertexBuffer + ) + // get handle to fragment shader's vColor member + mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor") + // Set color for drawing the triangle + GLES20.glUniform4fv(mColorHandle, 1, color, 0) + // get handle to shape's transformation matrix + mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix") + MyGLRenderer.checkGlError("glGetUniformLocation") + // Apply the projection and view transformation + GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0) + MyGLRenderer.checkGlError("glUniformMatrix4fv") + // Draw the square + GLES20.glDrawElements( + GLES20.GL_TRIANGLES, drawOrder.size, + GLES20.GL_UNSIGNED_SHORT, drawListBuffer + ) + // Disable vertex array + GLES20.glDisableVertexAttribArray(mPositionHandle) + } + + init { + // initialize vertex byte buffer for shape coordinates + val bb = ByteBuffer.allocateDirect( // (# of coordinate values * 4 bytes per float) + squareCoords.size * 4 + ) + bb.order(ByteOrder.nativeOrder()) + vertexBuffer = bb.asFloatBuffer() + vertexBuffer.put(squareCoords) + vertexBuffer.position(0) + // initialize byte buffer for the draw list + val dlb = ByteBuffer.allocateDirect( // (# of coordinate values * 2 bytes per short) + drawOrder.size * 2 + ) + dlb.order(ByteOrder.nativeOrder()) + drawListBuffer = dlb.asShortBuffer() + drawListBuffer.put(drawOrder) + drawListBuffer.position(0) + // prepare shaders and OpenGL program + val vertexShader = MyGLRenderer.loadShader( + GLES20.GL_VERTEX_SHADER, + vertexShaderCode + ) + val fragmentShader = MyGLRenderer.loadShader( + GLES20.GL_FRAGMENT_SHADER, + fragmentShaderCode + ) + mProgram = GLES20.glCreateProgram() // create empty OpenGL Program + GLES20.glAttachShader(mProgram, vertexShader) // add the vertex shader to program + GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment shader to program + GLES20.glLinkProgram(mProgram) // create OpenGL program executables + } + } + + private class Triangle { + private val vertexShaderCode = // This matrix member variable provides a hook to manipulate + // the coordinates of the objects that use this vertex shader + "uniform mat4 uMVPMatrix;" + + "attribute vec4 vPosition;" + + "void main() {" + // the matrix must be included as a modifier of gl_Position + // Note that the uMVPMatrix factor *must be first* in order + // for the matrix multiplication product to be correct. + " gl_Position = uMVPMatrix * vPosition;" + + "}" + + private val fragmentShaderCode = ( + "precision mediump float;" + + "uniform vec4 vColor;" + + "void main() {" + + " gl_FragColor = vColor;" + + "}" + ) + + private val vertexBuffer: FloatBuffer + private val mProgram: Int + private var mPositionHandle = 0 + private var mColorHandle = 0 + private var mMVPMatrixHandle = 0 + private val vertexCount = triangleCoords.size / COORDS_PER_VERTEX + private val vertexStride = COORDS_PER_VERTEX * 4 // 4 bytes per vertex + var color = floatArrayOf(1f, 0f, 0f, 0f) + + fun draw(mvpMatrix: FloatArray?) { + // Add program to OpenGL environment + GLES20.glUseProgram(mProgram) + // get handle to vertex shader's vPosition member + mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition") + // Enable a handle to the triangle vertices + GLES20.glEnableVertexAttribArray(mPositionHandle) + // Prepare the triangle coordinate data + GLES20.glVertexAttribPointer( + mPositionHandle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, + vertexStride, vertexBuffer + ) + // get handle to fragment shader's vColor member + mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor") + // Set color for drawing the triangle + GLES20.glUniform4fv(mColorHandle, 1, color, 0) + // get handle to shape's transformation matrix + mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix") + MyGLRenderer.checkGlError("glGetUniformLocation") + // Apply the projection and view transformation + GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0) + MyGLRenderer.checkGlError("glUniformMatrix4fv") + // Draw the triangle + GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount) + // Disable vertex array + GLES20.glDisableVertexAttribArray(mPositionHandle) + } + + companion object { + // number of coordinates per vertex in this array + val COORDS_PER_VERTEX = 3 + var triangleCoords = floatArrayOf( // in counterclockwise order: + 0.0f, 0.7f, 0.0f, // top + -0.7f, -0.3f, 0.0f, // bottom left + 0.7f, -0.3f, 0.0f // bottom right + ) + } + + init { + // initialize vertex byte buffer for shape coordinates + val bb = ByteBuffer.allocateDirect( // (number of coordinate values * 4 bytes per float) + triangleCoords.size * 4 + ) + // use the device hardware's native byte order + bb.order(ByteOrder.nativeOrder()) + // create a floating point buffer from the ByteBuffer + vertexBuffer = bb.asFloatBuffer() + // add the coordinates to the FloatBuffer + vertexBuffer.put(triangleCoords) + // set the buffer to read the first coordinate + vertexBuffer.position(0) + // prepare shaders and OpenGL program + val vertexShader = MyGLRenderer.loadShader( + GLES20.GL_VERTEX_SHADER, vertexShaderCode + ) + val fragmentShader = MyGLRenderer.loadShader( + GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode + ) + mProgram = GLES20.glCreateProgram() // create empty OpenGL Program + GLES20.glAttachShader(mProgram, vertexShader) // add the vertex shader to program + GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment shader to program + GLES20.glLinkProgram(mProgram) // create OpenGL program executables + } + } +} diff --git a/app/src/main/java/com/smartlook/app/view/TextRunDrawView.kt b/app/src/main/java/com/smartlook/app/view/TextRunDrawView.kt new file mode 100644 index 000000000..0edb6073f --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/TextRunDrawView.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.text.MeasuredText +import android.os.Build +import android.util.AttributeSet +import android.view.View +import androidx.annotation.RequiresApi +import com.smartlook.sdk.common.utils.dpToPxF + +class TextRunDrawView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + + init { + paint.textSize = dpToPxF(15f) + paint.textAlign = Paint.Align.CENTER + } + + override fun onDraw(canvas: Canvas) { + val contentWidth = width - paddingStart - paddingEnd + val contentHeight = height - paddingTop - paddingBottom + val lineCount = 9 + + val xCenter = paddingStart + contentWidth / 2f + val yCenter = paddingTop + contentHeight / 2f + val lineHeight = paint.textSize + val yStart = yCenter - (lineHeight * lineCount + LINE_MARGIN * (lineCount - 1)) / 2f - paint.ascent() + + var x = 0f + var y = yStart + + val drawLine: (draw: () -> Unit) -> Unit = { + it() + y += lineHeight + LINE_MARGIN + } + + val drawContent: () -> Unit = { + drawLine { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + drawCharSequence(canvas, x, y) + else + canvas.drawText("CharSequence run is not available on pre M", x, y, paint) + } + + drawLine { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + drawCharArray(canvas, x, y) + else + canvas.drawText("CharArray run is not available on pre M", x, y, paint) + } + + drawLine { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + drawMeasuredText(canvas, x, y) + else + canvas.drawText("MeasuredText run is not available on pre Q", x, y, paint) + } + } + + x = 0f + paint.textAlign = Paint.Align.LEFT + drawContent() + + x = xCenter + paint.textAlign = Paint.Align.CENTER + drawContent() + + x = width.toFloat() + paint.textAlign = Paint.Align.RIGHT + drawContent() + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun drawCharSequence(canvas: Canvas, x: Float, y: Float) { + val text = " Test message with CharSequence " + canvas.drawTextRun(text, 0, text.length, 0, text.length, x, y, false, paint) + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun drawCharArray(canvas: Canvas, x: Float, y: Float) { + val text = " Test message with CharArray ".toCharArray() + canvas.drawTextRun(text, 0, text.size, 0, text.size, x, y, false, paint) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun drawMeasuredText(canvas: Canvas, x: Float, y: Float) { + val text = " Test message with MeasuredText ".toCharArray() + val measuredText = MeasuredText.Builder(text) + .appendStyleRun(paint, text.size, false) + .build() + + canvas.drawTextRun(measuredText, 0, text.size, 0, text.size, x, y, false, paint) + } + + private companion object { + val LINE_MARGIN: Float = dpToPxF(10f) + } +} diff --git a/app/src/main/java/com/smartlook/app/view/bridge/TomasElement.kt b/app/src/main/java/com/smartlook/app/view/bridge/TomasElement.kt new file mode 100644 index 000000000..b9c8a5d41 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/bridge/TomasElement.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view.bridge + +import android.graphics.Rect + +sealed interface TomasElement { + + val isSensitive: Boolean + + data class Rectangle(val rect: Rect, val color: Int, override val isSensitive: Boolean) : TomasElement + + data class GradientRectangle(val rect: Rect, val topLeftColor: Int, val bottomRightColor: Int, override val isSensitive: Boolean) : TomasElement + + data class Circle(val x: Int, val y: Int, val radius: Int, val color: Int, override val isSensitive: Boolean) : TomasElement +} diff --git a/app/src/main/java/com/smartlook/app/view/bridge/TomasTextureView.kt b/app/src/main/java/com/smartlook/app/view/bridge/TomasTextureView.kt new file mode 100644 index 000000000..86ac12d57 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/bridge/TomasTextureView.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view.bridge + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Color +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.Shader +import android.graphics.SurfaceTexture +import android.util.AttributeSet +import android.view.Surface +import android.view.TextureView +import android.view.animation.BounceInterpolator +import androidx.core.animation.addListener + +class TomasTextureView(context: Context, attrs: AttributeSet? = null) : TextureView(context, attrs) { + + private val paint = Paint() + + private var surface: Surface? = null + + private val animator = ValueAnimator.ofFloat(0f, 1f) + private var isCanceled = false + private val rectTemp = Rect() + private var transitionX = 0 + + val elements = ArrayList() + + var listener: Listener? = null + + init { + surfaceTextureListener = MySurfaceTextureListener() + // TODO out of scope for GA +// elements += TomasElement.Rectangle(Rect(0f.px, 0f.px, 150f.px, 20f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 25f.px, 125f.px, 45f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 50f.px, 95f.px, 120f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(100f.px, 50f.px, 175f.px, 70f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(100f.px, 75f.px, 150f.px, 95f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(100f.px, 100f.px, 130f.px, 120f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 125f.px, 150f.px, 145f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 150f.px, 185f.px, 170f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 175f.px, 190f.px, 195f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 200f.px, 150f.px, 220f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Rectangle(Rect(0f.px, 225f.px, 185f.px, 245f.px), 0xffff00ff.toInt(), true) +// elements += TomasElement.Circle(80f.px, 70f.px, 30f.px, 0xff0000aa.toInt(), false) +// elements += TomasElement.Rectangle(Rect(25f.px, 130f.px, 125f.px, 230f.px), 0xff0000ff.toInt(), false) +// elements += TomasElement.GradientRectangle(Rect(25f.px, 130f.px, 125f.px, 230f.px), 0xff0000ff.toInt(), 0xffff0000.toInt(), false) +// +// animator.addUpdateListener { +// val fraction = it.animatedValue as Float +// +// transitionX = if (animator.currentPlayTime > 1000L) +// ((1f - INTERPOLATOR.getInterpolation(1f - fraction)) * 50f.px).toInt() +// else +// (INTERPOLATOR.getInterpolation(fraction) * 50f.px).toInt() +// +// redraw() +// } + + animator.addListener( + onStart = { + listener?.onTransitionChanged(true) + }, + onEnd = { + listener?.onTransitionChanged(false) + + if (isAttachedToWindow && !isCanceled) + animator.start() + } + ) + + animator.repeatMode = ValueAnimator.REVERSE + animator.startDelay = 5000L + animator.duration = 1000L + animator.repeatCount = 1 + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + isCanceled = false + animator.start() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + isCanceled = true + animator.cancel() + } + + private fun redraw() { + val surface = surface ?: return + val canvas = surface.lockCanvas(null) + + canvas.drawColor(Color.WHITE) + + for (element in elements) + when (element) { + is TomasElement.Rectangle -> { + paint.color = element.color + rectTemp.set(element.rect) + rectTemp.offset(transitionX, 0) + canvas.drawRect(rectTemp, paint) + } + is TomasElement.Circle -> { + paint.color = element.color + canvas.drawCircle(element.x.toFloat() + transitionX, element.y.toFloat(), element.radius.toFloat(), paint) + } + is TomasElement.GradientRectangle -> { + paint.shader = LinearGradient(element.rect.left.toFloat(), element.rect.top.toFloat(), element.rect.right.toFloat(), element.rect.bottom.toFloat(), element.topLeftColor, element.bottomRightColor, Shader.TileMode.CLAMP) + rectTemp.set(element.rect) + rectTemp.offset(transitionX, 0) + canvas.drawRect(rectTemp, paint) + paint.shader = null + } + } + + surface.unlockCanvasAndPost(canvas) + } + + private inner class MySurfaceTextureListener : SurfaceTextureListener { + + override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { + surface = Surface(surfaceTexture) + redraw() + } + + override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean { + surface = null + return true + } + + override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) {} + + override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) {} + } + + interface Listener { + fun onTransitionChanged(isRunning: Boolean) + } + + private companion object { + val INTERPOLATOR = BounceInterpolator() + } +} diff --git a/app/src/main/java/com/smartlook/app/view/bridge/TomasView.kt b/app/src/main/java/com/smartlook/app/view/bridge/TomasView.kt new file mode 100644 index 000000000..d1cf0e160 --- /dev/null +++ b/app/src/main/java/com/smartlook/app/view/bridge/TomasView.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2024 Splunk Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartlook.app.view.bridge + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout + +class TomasView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { + + private val textureView = TomasTextureView(context) + + val elements: List + get() = textureView.elements + + var listener: Listener? = null + + init { + textureView.listener = TomasTextureViewListener() + addView(textureView) + } + + private inner class TomasTextureViewListener : TomasTextureView.Listener { + override fun onTransitionChanged(isRunning: Boolean) { + listener?.onTransitionChanged(isRunning) + } + } + + interface Listener { + fun onTransitionChanged(isRunning: Boolean) + } +} diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 000000000..9cc4a7c88 --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 000000000..477a22bbc --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slow_enter_from_left.xml b/app/src/main/res/anim/slow_enter_from_left.xml new file mode 100644 index 000000000..6dca51855 --- /dev/null +++ b/app/src/main/res/anim/slow_enter_from_left.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slow_enter_from_right.xml b/app/src/main/res/anim/slow_enter_from_right.xml new file mode 100644 index 000000000..138300c7a --- /dev/null +++ b/app/src/main/res/anim/slow_enter_from_right.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slow_exit_to_left.xml b/app/src/main/res/anim/slow_exit_to_left.xml new file mode 100644 index 000000000..3b59b3467 --- /dev/null +++ b/app/src/main/res/anim/slow_exit_to_left.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slow_exit_to_right.xml b/app/src/main/res/anim/slow_exit_to_right.xml new file mode 100644 index 000000000..26d22a571 --- /dev/null +++ b/app/src/main/res/anim/slow_exit_to_right.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/slow_fade_in.xml b/app/src/main/res/animator/slow_fade_in.xml new file mode 100644 index 000000000..eae8eae99 --- /dev/null +++ b/app/src/main/res/animator/slow_fade_in.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/animator/slow_fade_out.xml b/app/src/main/res/animator/slow_fade_out.xml new file mode 100644 index 000000000..fc1fce774 --- /dev/null +++ b/app/src/main/res/animator/slow_fade_out.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/app_icon.xml b/app/src/main/res/drawable/app_icon.xml new file mode 100644 index 000000000..67fb14160 --- /dev/null +++ b/app/src/main/res/drawable/app_icon.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bcg_gradient_blue.xml b/app/src/main/res/drawable/bcg_gradient_blue.xml new file mode 100644 index 000000000..13710f2db --- /dev/null +++ b/app/src/main/res/drawable/bcg_gradient_blue.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 000000000..dcf7b248a --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 000000000..69c32781d --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_bitmap_big.png b/app/src/main/res/drawable/ic_bitmap_big.png new file mode 100644 index 0000000000000000000000000000000000000000..06a5e4c226eecb20a8f363d6c20d32beba6f80c5 GIT binary patch literal 15053 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 OhFl#ze`<7vXaCuV+2QK| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_bitmap_sample_2.jpg b/app/src/main/res/drawable/ic_bitmap_sample_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7b63a4fa452382d5d524a92480f82dbd91d0ffb2 GIT binary patch literal 37525 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

&PI)!G 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 cQHRMg+ejl<-1d-L**V4ULjV8( literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_bitmap_sample_3.jpg b/app/src/main/res/drawable/ic_bitmap_sample_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fe35fc95aaa8ce56c3c33f5da8cb99f393082512 GIT binary patch literal 69518 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 Y`?<_slCw0Kx2H7Y001#EzyJUM08rr(&Hw-a literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_bitmap_sample_4.jpg b/app/src/main/res/drawable/ic_bitmap_sample_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4bc0593f97a22c1446462e96f3dc9c73b4dcd460 GIT binary patch literal 21329 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 Vj&INQW6K-U+&=PTN6+?u|JfJW`^5kN literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_bitmap_sample_5.jpg b/app/src/main/res/drawable/ic_bitmap_sample_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8142d5141cd393caee2ee9d6da8d31c2c582bcb7 GIT binary patch literal 22944 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 bgGSz4B)3&`XxFR7p|#7U*OM8w2mk;8gFCXF literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_bitmap_sample_6.jpg b/app/src/main/res/drawable/ic_bitmap_sample_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25320f2b1c9b2e8e77446b4e797fcfe2287d6c6d GIT binary patch literal 39866 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

rrmz&o5X;gy^$o8sXm=2OpaL^azl77`t> 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 ZzX0gw%X!C5h&BKK002ovPDHLkV1mC;m*oHe literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_crop_test.xml b/app/src/main/res/drawable/ic_crop_test.xml new file mode 100644 index 000000000..077c0e84f --- /dev/null +++ b/app/src/main/res/drawable/ic_crop_test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_gradient.xml b/app/src/main/res/drawable/ic_gradient.xml new file mode 100644 index 000000000..21017787f --- /dev/null +++ b/app/src/main/res/drawable/ic_gradient.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_progress.xml b/app/src/main/res/drawable/ic_progress.xml new file mode 100644 index 000000000..d63e828c2 --- /dev/null +++ b/app/src/main/res/drawable/ic_progress.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_select_all.xml b/app/src/main/res/drawable/ic_select_all.xml new file mode 100644 index 000000000..d08a695db --- /dev/null +++ b/app/src/main/res/drawable/ic_select_all.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_tab_dot.xml b/app/src/main/res/drawable/ic_tab_dot.xml new file mode 100644 index 000000000..2d74503b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_tab_dot.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_test_rect_1.xml b/app/src/main/res/drawable/ic_test_rect_1.xml new file mode 100644 index 000000000..9542a2cf5 --- /dev/null +++ b/app/src/main/res/drawable/ic_test_rect_1.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_test_rect_2.xml b/app/src/main/res/drawable/ic_test_rect_2.xml new file mode 100644 index 000000000..7408f90de --- /dev/null +++ b/app/src/main/res/drawable/ic_test_rect_2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_vector_big.xml b/app/src/main/res/drawable/ic_vector_big.xml new file mode 100644 index 000000000..1fc8f060e --- /dev/null +++ b/app/src/main/res/drawable/ic_vector_big.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_vector_small.xml b/app/src/main/res/drawable/ic_vector_small.xml new file mode 100644 index 000000000..67fb14160 --- /dev/null +++ b/app/src/main/res/drawable/ic_vector_small.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sel_button.xml b/app/src/main/res/drawable/sel_button.xml new file mode 100644 index 000000000..047bce751 --- /dev/null +++ b/app/src/main/res/drawable/sel_button.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sel_text.xml b/app/src/main/res/drawable/sel_text.xml new file mode 100644 index 000000000..c869b297a --- /dev/null +++ b/app/src/main/res/drawable/sel_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_empty.xml b/app/src/main/res/layout/activity_empty.xml new file mode 100644 index 000000000..0faa5bdb0 --- /dev/null +++ b/app/src/main/res/layout/activity_empty.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..f2aa764c7 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_bridge_interface.xml b/app/src/main/res/layout/fragment_bridge_interface.xml new file mode 100644 index 000000000..128bf6cc5 --- /dev/null +++ b/app/src/main/res/layout/fragment_bridge_interface.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_collapsing_layout.xml b/app/src/main/res/layout/fragment_collapsing_layout.xml new file mode 100644 index 000000000..2ccdcb54d --- /dev/null +++ b/app/src/main/res/layout/fragment_collapsing_layout.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dialog.xml b/app/src/main/res/layout/fragment_dialog.xml new file mode 100644 index 000000000..b51091558 --- /dev/null +++ b/app/src/main/res/layout/fragment_dialog.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dummy.xml b/app/src/main/res/layout/fragment_dummy.xml new file mode 100644 index 000000000..856e1c368 --- /dev/null +++ b/app/src/main/res/layout/fragment_dummy.xml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_http_url_connection.xml b/app/src/main/res/layout/fragment_http_url_connection.xml new file mode 100644 index 000000000..eae3e05e8 --- /dev/null +++ b/app/src/main/res/layout/fragment_http_url_connection.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + +