From b58412a0c4146c10d005059385e00e639c8a4f49 Mon Sep 17 00:00:00 2001 From: Kamil Gurgul Date: Tue, 3 Oct 2017 08:28:30 +0200 Subject: [PATCH] Create Github repo --- LICENSE | 202 ++++++ README.md | 54 ++ app/.gitignore | 4 + app/build.gradle | 143 ++++ app/proguard-rules.pro | 36 + app/src/main/AndroidManifest.xml | 69 ++ .../content/pm/IPackageStatsObserver.aidl | 11 + .../aidl/android/content/pm/PackageStats.aidl | 4 + .../java/com/kgurgul/cpuinfo/CpuInfoApp.kt | 65 ++ .../cpuinfo/analytics/AnalyticsEvents.kt | 31 + .../cpuinfo/analytics/AnalyticsManager.kt | 94 +++ .../java/com/kgurgul/cpuinfo/common/Prefs.kt | 74 ++ .../cpuinfo/common/list/AdapterArrayList.kt | 123 ++++ .../common/list/DividerItemDecoration.kt | 71 ++ .../common/list/SimpleListChangeNotifier.kt | 49 ++ .../com/kgurgul/cpuinfo/di/Annotations.kt | 37 + .../com/kgurgul/cpuinfo/di/AppInjectior.kt | 80 +++ .../java/com/kgurgul/cpuinfo/di/Injectable.kt | 24 + .../cpuinfo/di/ViewModelInjectionFactory.kt | 34 + .../cpuinfo/di/components/AppComponent.kt | 48 ++ .../cpuinfo/di/modules/ActivityModule.kt | 33 + .../kgurgul/cpuinfo/di/modules/AppModule.kt | 99 +++ .../di/modules/FragmentBuildersModule.kt | 82 +++ .../kgurgul/cpuinfo/features/HostActivity.kt | 174 +++++ .../cpuinfo/features/NavigationController.kt | 104 +++ .../applications/ApplicationsAdapter.kt | 153 +++++ .../applications/ApplicationsFragment.kt | 237 +++++++ .../applications/ApplicationsViewModel.kt | 170 +++++ .../features/applications/ExtendedAppInfo.kt | 54 ++ .../applications/StorageUsageService.kt | 84 +++ .../information/ContainerInfoFragment.kt | 53 ++ .../android/AndroidInfoFragment.kt | 49 ++ .../android/AndroidInfoViewModel.kt | 158 +++++ .../information/base/BaseRvFragment.kt | 63 ++ .../information/base/InfoItemsAdapter.kt | 92 +++ .../information/base/ViewPagerAdapter.kt | 70 ++ .../information/cpu/CpuInfoFragment.kt | 59 ++ .../information/cpu/CpuInfoViewModel.kt | 238 +++++++ .../information/gpu/GpuInfoFragment.kt | 105 +++ .../information/gpu/GpuInfoViewModel.kt | 74 ++ .../hardware/BatteryStatusProvider.kt | 65 ++ .../hardware/HardwareInfoFragment.kt | 75 ++ .../hardware/HardwareInfoViewModel.kt | 342 ++++++++++ .../information/ram/ClearRamAsyncTask.kt | 85 +++ .../information/ram/RamInfoFragment.kt | 98 +++ .../information/ram/RamInfoViewModel.kt | 139 ++++ .../information/screen/ScreenInfoFragment.kt | 49 ++ .../information/screen/ScreenInfoViewModel.kt | 162 +++++ .../sensors/SensorsInfoFragment.kt | 59 ++ .../sensors/SensorsInfoViewModel.kt | 154 +++++ .../information/storage/StorageAdapter.kt | 87 +++ .../storage/StorageInfoFragment.kt | 99 +++ .../storage/StorageInfoViewModel.kt | 243 +++++++ .../information/storage/StorageItem.kt | 27 + .../cpuinfo/features/processes/ProcessItem.kt | 25 + .../features/processes/ProcessesAdapter.kt | 85 +++ .../features/processes/ProcessesFragment.kt | 104 +++ .../features/processes/ProcessesViewModel.kt | 179 +++++ .../features/settings/SettingsFragment.kt | 73 ++ .../temperature/TemperatureFormatter.kt | 49 ++ .../temperature/TemperatureFragment.kt | 102 +++ .../features/temperature/TemperatureModule.kt | 40 ++ .../temperature/TemperatureProvider.kt | 117 ++++ .../temperature/TemperatureViewModel.kt | 180 +++++ .../temperature/list/TemperatureAdapter.kt | 73 ++ .../temperature/list/TemperatureItem.kt | 22 + .../list/TemperatureItemViewModel.kt | 57 ++ .../kgurgul/cpuinfo/utils/AutoClearedValue.kt | 44 ++ .../com/kgurgul/cpuinfo/utils/Extensions.kt | 64 ++ .../cpuinfo/utils/SingleLiveEvent.java | 77 +++ .../java/com/kgurgul/cpuinfo/utils/Utils.kt | 116 ++++ .../processmanager/AndroidProcesses.java | 297 ++++++++ .../models/AndroidAppProcess.java | 151 ++++ .../processmanager/models/AndroidProcess.java | 628 +++++++++++++++++ .../utils/processmanager/models/Cgroup.java | 110 +++ .../processmanager/models/ControlGroup.java | 80 +++ .../utils/processmanager/models/ProcFile.java | 94 +++ .../utils/processmanager/models/Stat.java | 645 ++++++++++++++++++ .../utils/processmanager/models/Statm.java | 96 +++ .../utils/processmanager/models/Status.java | 204 ++++++ .../cpuinfo/widgets/arc/ArcProgress.java | 378 ++++++++++ .../progress/BaseRoundCornerProgressBar.java | 386 +++++++++++ .../progress/IconRoundCornerProgressBar.java | 356 ++++++++++ .../ramwidget/RamUsageWidgetProvider.kt | 163 +++++ .../widgets/ramwidget/RefreshService.kt | 118 ++++ .../widgets/ramwidget/events/Events.kt | 22 + .../swiperv/SwipeHorizontalMenuLayout.java | 352 ++++++++++ .../widgets/swiperv/SwipeMenuHelper.java | 142 ++++ .../widgets/swiperv/SwipeMenuLayout.java | 186 +++++ .../swiperv/SwipeMenuRecyclerView.java | 84 +++ .../listener/SimpleSwipeFractionListener.java | 34 + .../listener/SimpleSwipeSwitchListener.java | 45 ++ .../listener/SwipeFractionListener.java | 28 + .../swiperv/listener/SwipeSwitchListener.java | 34 + .../swiperv/swiper/BottomVerticalSwiper.java | 72 ++ .../swiperv/swiper/LeftHorizontalSwiper.java | 72 ++ .../swiperv/swiper/RightHorizontalSwiper.java | 72 ++ .../widgets/swiperv/swiper/Swiper.java | 74 ++ .../swiperv/swiper/TopVerticalSwiper.java | 72 ++ .../res/drawable-hdpi/nav_drawer_header.png | Bin 0 -> 36293 bytes .../round_corner_progress_icon.png | Bin 0 -> 444 bytes .../res/drawable-mdpi/nav_drawer_header.png | Bin 0 -> 20795 bytes .../round_corner_progress_icon.png | Bin 0 -> 263 bytes .../main/res/drawable-xhdpi/c_plus_plus.png | Bin 0 -> 1475 bytes app/src/main/res/drawable-xhdpi/folder.png | Bin 0 -> 181 bytes .../res/drawable-xhdpi/nav_drawer_header.png | Bin 0 -> 49803 bytes .../main/res/drawable-xhdpi/ram_preview.png | Bin 0 -> 45270 bytes app/src/main/res/drawable-xhdpi/root.png | Bin 0 -> 2133 bytes .../round_corner_progress_icon.png | Bin 0 -> 604 bytes app/src/main/res/drawable-xhdpi/sdcard.png | Bin 0 -> 784 bytes .../res/drawable-xxhdpi/nav_drawer_header.png | Bin 0 -> 82253 bytes .../round_corner_progress_icon.png | Bin 0 -> 1273 bytes .../drawable-xxxhdpi/nav_drawer_header.png | Bin 0 -> 127811 bytes app/src/main/res/drawable/ic_android.xml | 25 + app/src/main/res/drawable/ic_battery.xml | 25 + app/src/main/res/drawable/ic_cpu_temp.xml | 62 ++ app/src/main/res/drawable/ic_hardware.xml | 62 ++ app/src/main/res/drawable/ic_process.xml | 25 + app/src/main/res/drawable/ic_settings.xml | 25 + app/src/main/res/drawable/ic_sort.xml | 25 + app/src/main/res/drawable/ic_temperature.xml | 25 + app/src/main/res/drawable/ic_thrash.xml | 25 + .../main/res/layout/activity_host_layout.xml | 60 ++ .../main/res/layout/dialog_native_libs.xml | 43 ++ .../main/res/layout/fragment_applications.xml | 52 ++ app/src/main/res/layout/fragment_info.xml | 40 ++ .../main/res/layout/fragment_processes.xml | 50 ++ app/src/main/res/layout/fragment_rv.xml | 34 + .../main/res/layout/fragment_temperature.xml | 65 ++ app/src/main/res/layout/header_nav_drawer.xml | 53 ++ app/src/main/res/layout/item_application.xml | 81 +++ app/src/main/res/layout/item_native_libs.xml | 25 + .../res/layout/item_preference_category.xml | 21 + app/src/main/res/layout/item_process.xml | 119 ++++ app/src/main/res/layout/item_storage.xml | 51 ++ app/src/main/res/layout/item_swipe_app.xml | 33 + .../main/res/layout/item_swipe_app_right.xml | 41 ++ app/src/main/res/layout/item_temperature.xml | 76 +++ app/src/main/res/layout/item_value.xml | 44 ++ .../main/res/layout/item_value_vertical.xml | 41 ++ .../layout_icon_round_corner_progress_bar.xml | 56 ++ app/src/main/res/layout/widget.xml | 44 ++ app/src/main/res/menu/apps_menu.xml | 27 + app/src/main/res/menu/navigation_menu.xml | 54 ++ app/src/main/res/menu/process_menu.xml | 27 + app/src/main/res/menu/ram_menu.xml | 27 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 20 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 20 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 7964 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 5842 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3766 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3874 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 2431 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2444 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 11296 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 7407 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5301 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 21009 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 15519 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8802 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 31876 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 21998 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 11683 bytes app/src/main/res/values-hdpi/dimens.xml | 25 + app/src/main/res/values-pl/strings.xml | 141 ++++ app/src/main/res/values-sw600dp/dimens.xml | 25 + app/src/main/res/values-v14/dimens.xml | 21 + app/src/main/res/values-v21/styles.xml | 35 + app/src/main/res/values-xhdpi/dimens.xml | 25 + app/src/main/res/values-xxhdpi/dimens.xml | 25 + app/src/main/res/values-xxxhdpi/dimens.xml | 25 + app/src/main/res/values/arrays.xml | 41 ++ app/src/main/res/values/attrs.xml | 48 ++ app/src/main/res/values/colors.xml | 30 + app/src/main/res/values/dimens.xml | 27 + .../res/values/ic_launcher_background.xml | 19 + app/src/main/res/values/ids.xml | 23 + app/src/main/res/values/strings.xml | 143 ++++ app/src/main/res/values/styles.xml | 60 ++ app/src/main/res/values/values.xml | 51 ++ app/src/main/res/xml/preferences.xml | 45 ++ app/src/main/res/xml/ram_widget_provider.xml | 23 + build.gradle | 20 + gradle.properties | 19 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++ gradlew.bat | 90 +++ info/google-play-badge.png | Bin 0 -> 21058 bytes info/icon_glow.png | Bin 0 -> 47343 bytes settings.gradle | 1 + 191 files changed, 14121 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/aidl/android/content/pm/IPackageStatsObserver.aidl create mode 100644 app/src/main/aidl/android/content/pm/PackageStats.aidl create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/CpuInfoApp.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsEvents.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsManager.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/common/Prefs.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/common/list/AdapterArrayList.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/common/list/DividerItemDecoration.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/common/list/SimpleListChangeNotifier.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/Annotations.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/AppInjectior.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/Injectable.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/ViewModelInjectionFactory.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/components/AppComponent.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/modules/ActivityModule.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/modules/AppModule.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/di/modules/FragmentBuildersModule.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/HostActivity.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/NavigationController.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsAdapter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/applications/ExtendedAppInfo.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/applications/StorageUsageService.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/ContainerInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/base/BaseRvFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/base/InfoItemsAdapter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/base/ViewPagerAdapter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/BatteryStatusProvider.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/ClearRamAsyncTask.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageAdapter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageItem.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessItem.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesAdapter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/settings/SettingsFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFormatter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFragment.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureModule.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureProvider.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureAdapter.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItem.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItemViewModel.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/utils/AutoClearedValue.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/utils/Extensions.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/utils/SingleLiveEvent.java create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/utils/Utils.kt create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/AndroidProcesses.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidAppProcess.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidProcess.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Cgroup.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ControlGroup.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ProcFile.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Stat.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Statm.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Status.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/arc/ArcProgress.java create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/BaseRoundCornerProgressBar.java create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/IconRoundCornerProgressBar.java create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RamUsageWidgetProvider.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RefreshService.kt create mode 100644 app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/events/Events.kt create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeHorizontalMenuLayout.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuHelper.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuLayout.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuRecyclerView.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeFractionListener.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeSwitchListener.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeFractionListener.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeSwitchListener.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/BottomVerticalSwiper.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/LeftHorizontalSwiper.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/RightHorizontalSwiper.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/Swiper.java create mode 100755 app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/TopVerticalSwiper.java create mode 100644 app/src/main/res/drawable-hdpi/nav_drawer_header.png create mode 100644 app/src/main/res/drawable-hdpi/round_corner_progress_icon.png create mode 100644 app/src/main/res/drawable-mdpi/nav_drawer_header.png create mode 100644 app/src/main/res/drawable-mdpi/round_corner_progress_icon.png create mode 100644 app/src/main/res/drawable-xhdpi/c_plus_plus.png create mode 100644 app/src/main/res/drawable-xhdpi/folder.png create mode 100644 app/src/main/res/drawable-xhdpi/nav_drawer_header.png create mode 100644 app/src/main/res/drawable-xhdpi/ram_preview.png create mode 100644 app/src/main/res/drawable-xhdpi/root.png create mode 100644 app/src/main/res/drawable-xhdpi/round_corner_progress_icon.png create mode 100644 app/src/main/res/drawable-xhdpi/sdcard.png create mode 100644 app/src/main/res/drawable-xxhdpi/nav_drawer_header.png create mode 100644 app/src/main/res/drawable-xxhdpi/round_corner_progress_icon.png create mode 100644 app/src/main/res/drawable-xxxhdpi/nav_drawer_header.png create mode 100644 app/src/main/res/drawable/ic_android.xml create mode 100644 app/src/main/res/drawable/ic_battery.xml create mode 100644 app/src/main/res/drawable/ic_cpu_temp.xml create mode 100644 app/src/main/res/drawable/ic_hardware.xml create mode 100644 app/src/main/res/drawable/ic_process.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/ic_sort.xml create mode 100644 app/src/main/res/drawable/ic_temperature.xml create mode 100644 app/src/main/res/drawable/ic_thrash.xml create mode 100644 app/src/main/res/layout/activity_host_layout.xml create mode 100644 app/src/main/res/layout/dialog_native_libs.xml create mode 100644 app/src/main/res/layout/fragment_applications.xml create mode 100644 app/src/main/res/layout/fragment_info.xml create mode 100644 app/src/main/res/layout/fragment_processes.xml create mode 100644 app/src/main/res/layout/fragment_rv.xml create mode 100644 app/src/main/res/layout/fragment_temperature.xml create mode 100644 app/src/main/res/layout/header_nav_drawer.xml create mode 100644 app/src/main/res/layout/item_application.xml create mode 100644 app/src/main/res/layout/item_native_libs.xml create mode 100644 app/src/main/res/layout/item_preference_category.xml create mode 100644 app/src/main/res/layout/item_process.xml create mode 100644 app/src/main/res/layout/item_storage.xml create mode 100644 app/src/main/res/layout/item_swipe_app.xml create mode 100644 app/src/main/res/layout/item_swipe_app_right.xml create mode 100644 app/src/main/res/layout/item_temperature.xml create mode 100644 app/src/main/res/layout/item_value.xml create mode 100644 app/src/main/res/layout/item_value_vertical.xml create mode 100644 app/src/main/res/layout/layout_icon_round_corner_progress_bar.xml create mode 100644 app/src/main/res/layout/widget.xml create mode 100644 app/src/main/res/menu/apps_menu.xml create mode 100644 app/src/main/res/menu/navigation_menu.xml create mode 100644 app/src/main/res/menu/process_menu.xml create mode 100644 app/src/main/res/menu/ram_menu.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 100755 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100755 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100755 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100755 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100755 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-hdpi/dimens.xml create mode 100644 app/src/main/res/values-pl/strings.xml create mode 100644 app/src/main/res/values-sw600dp/dimens.xml create mode 100644 app/src/main/res/values-v14/dimens.xml create mode 100644 app/src/main/res/values-v21/styles.xml create mode 100644 app/src/main/res/values-xhdpi/dimens.xml create mode 100644 app/src/main/res/values-xxhdpi/dimens.xml create mode 100644 app/src/main/res/values-xxxhdpi/dimens.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100755 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/dimens.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100755 app/src/main/res/values/ids.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/values.xml create mode 100644 app/src/main/res/xml/preferences.xml create mode 100644 app/src/main/res/xml/ram_widget_provider.xml create mode 100644 build.gradle 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 info/google-play-badge.png create mode 100644 info/icon_glow.png create mode 100644 settings.gradle diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..62aad3fb --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +
+CPU Info +======== +CPU Info provides information about Android device hardware and software. +This project will be next release version after full migration into new MVVM +architecture. Most of the code is written in Kotlin but some old widgets are +still in Java. + +Current version can be found on Google Play:
+[![Get it on Google Play](info/google-play-badge.png)](https://play.google.com/store/apps/details?id=com.kgurgul.cpuinfo) + +Building requirements +===================== +* *res/xml/app_tracker.xml* for Google Analytics +* *google-services.json* file with Firebase configuration + +Used libraries +============== +* [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html) +* [RxJava 2](https://github.com/ReactiveX/RxJava) +* [Dagger 2](https://github.com/google/dagger) +* [Gson](https://github.com/google/gson) +* [EventBus](https://github.com/greenrobot/EventBus) +* Small parts from [1](https://github.com/lzyzsd/CircleProgress), [2](https://github.com/akexorcist/Android-RoundCornerProgressBar), +[3](https://github.com/jaredrummler/AndroidProcesses), [4](https://github.com/TUBB/SwipeMenu) + +Must have before 3.0 release +============================ +* Fix or deletion of RAM widget for Android O +* Code cleanup +* Android Architecture Components stable version +* Global testing + +Still TODO +========== +* Migration all heavy lifting into coroutines (remove AsyncTask) +* Tests +* Travis integration + +License +------- + Copyright 2017 KG Soft + + 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. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..3aba1332 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,4 @@ +/build +src/debug/google-services.json +src/release/google-services.json +src/main/res/xml/app_tracker.xml \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..6388c630 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,143 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +Properties signingProperties = getReleaseSigningConfig() + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + + defaultConfig { + applicationId "com.kgurgul.cpuinfo" + minSdkVersion 14 + targetSdkVersion 26 + versionCode 30 + versionName "3.0" + vectorDrawables.useSupportLibrary = true + } + + signingConfigs { + releaseVersion { + storeFile file(signingProperties.getProperty('keyPath')) + storePassword signingProperties.getProperty('keyPass') + keyAlias signingProperties.getProperty('keyAlias') + keyPassword signingProperties.getProperty('keyPass') + } + } + + buildTypes { + release { + minifyEnabled true + signingConfig signingConfigs.releaseVersion + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled false + applicationIdSuffix ".debug" + } + } + + lintOptions { + abortOnError false + } + + compileOptions.incremental = true + + dataBinding { + enabled = true + } +} + +kapt { + generateStubs = true +} + +ext { + supportVersion = "26.1.0" + playServicesVersion = "11.4.0" + androidPluginVersion = "2.3.0" + archLifecycleVersion = "1.0.0-beta1" + daggerVersion = "2.11" + ankoVersion = "0.10.1" +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + + testCompile 'junit:junit:4.12' + + compile "com.android.support:appcompat-v7:$supportVersion" + compile "com.android.support:design:$supportVersion" + compile "com.android.support:preference-v14:$supportVersion" + + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile "org.jetbrains.anko:anko-coroutines:$ankoVersion" + + // Android data binding + kapt "com.android.databinding:compiler:$androidPluginVersion" + + // Play services and Firebase + compile "com.google.android.gms:play-services-analytics:$playServicesVersion" + releaseCompile "com.google.firebase:firebase-crash:$playServicesVersion" + + // Bus + compile 'org.greenrobot:eventbus:3.0.0' + + // Rx + compile 'io.reactivex.rxjava2:rxjava:2.1.3' + compile 'io.reactivex.rxjava2:rxandroid:2.0.1' + compile 'io.reactivex.rxjava2:rxkotlin:2.0.0' + + // GSON + compile 'com.google.code.gson:gson:2.8.0' + + // Dagger + compile "com.google.dagger:dagger:$daggerVersion" + compile "com.google.dagger:dagger-android-support:$daggerVersion" + kapt "com.google.dagger:dagger-compiler:$daggerVersion" + kapt "com.google.dagger:dagger-android-processor:$daggerVersion" + + // Timber + compile 'com.jakewharton.timber:timber:4.5.1' + + // Google arch + compile "android.arch.lifecycle:extensions:$archLifecycleVersion" + annotationProcessor "android.arch.lifecycle:compiler:$archLifecycleVersion" + + // Leak canary +/* debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.3' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.3'*/ +} + +buildscript { + ext.kotlin_version = '1.1.51' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} +repositories { + mavenCentral() +} + +def getReleaseSigningConfig() { + Properties properties = new Properties() + try { + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + } catch (FileNotFoundException e) { + def debugKeyDir = System.getenv('HOME') + "/.android/debug.keystore" + properties.setProperty('keyPath', debugKeyDir) + properties.setProperty('keyPass', 'android') + properties.setProperty('keyAlias', 'androiddebugkey') + properties.setProperty('keyPass', 'android') + + logger.warn("Using default debug key instead of release key!") + } + return properties +} + + +apply plugin: 'com.google.gms.google-services' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..1aa64180 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,36 @@ +# Kotlin +-dontwarn kotlin.** + +-keepclassmembers class **$WhenMappings { + ; +} + +# RxJava +-dontwarn com.google.errorprone.annotations.* + +# EventBus +#-keepattributes *Annotation* +-keepclassmembers class ** { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +# Only required if you use AsyncExecutor +-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { + (java.lang.Throwable); +} + +# General +-renamesourcefileattribute SourceFile +-keepattributes Exceptions,InnerClasses,Signature,SourceFile,LineNumberTable,*Annotation* + +# AIDL +-keep class android.content.pm.IPackageStatsObserver$** { + public ; + public ; +} +-keep class android.content.pm.IPackageStatsObserver$Stub.** { + public ; + public ; +} +-keep interface android.content.pm.IPackageStatsObserver$** {*;} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3dc4936d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageStatsObserver.aidl b/app/src/main/aidl/android/content/pm/IPackageStatsObserver.aidl new file mode 100644 index 00000000..1c0cd092 --- /dev/null +++ b/app/src/main/aidl/android/content/pm/IPackageStatsObserver.aidl @@ -0,0 +1,11 @@ +package android.content.pm; +import android.content.pm.PackageStats; +/** + * API for package data change related callbacks from the Package Manager. + * Some usage scenarios include deletion of cache directory, generate + * statistics related to code, data, cache usage + */ +oneway interface IPackageStatsObserver { + + void onGetStatsCompleted(in PackageStats pStats, boolean succeeded); +} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/PackageStats.aidl b/app/src/main/aidl/android/content/pm/PackageStats.aidl new file mode 100644 index 00000000..8f472a3c --- /dev/null +++ b/app/src/main/aidl/android/content/pm/PackageStats.aidl @@ -0,0 +1,4 @@ +// PackageStats.aidl +package android.content.pm; + +parcelable PackageStats; diff --git a/app/src/main/java/com/kgurgul/cpuinfo/CpuInfoApp.kt b/app/src/main/java/com/kgurgul/cpuinfo/CpuInfoApp.kt new file mode 100644 index 00000000..1a316a24 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/CpuInfoApp.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo + +import android.app.Activity +import android.app.Application +import com.kgurgul.cpuinfo.di.AppInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasActivityInjector +import timber.log.Timber +import javax.inject.Inject + +/** + * Base Application class for required initializations + * + * @author kgurgul + */ +class CpuInfoApp : Application(), HasActivityInjector { + + companion object { + lateinit var instance: CpuInfoApp + } + + @Inject + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector + + override fun onCreate() { + super.onCreate() + instance = this + + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } + + AppInjector.init(this) + } + + // TODO: Fix widget +/* fun updateRamWidget(app: Application) { + Timber.d("Broadcast with ram widget update sent") + + val intent = Intent(app, RamUsageWidgetProvider::class.java) + intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + val ids = intArrayOf(R.xml.ram_widget_provider) + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) + app.sendBroadcast(intent) + }*/ + + override fun activityInjector(): DispatchingAndroidInjector + = dispatchingAndroidInjector +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsEvents.kt b/app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsEvents.kt new file mode 100644 index 00000000..61b0581b --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsEvents.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.analytics + +/** + * Container for analytics events + * + * @author kgurgul + */ +object AnalyticsEvents { + // Screen names + val INFO_SCREEN = "Info Screen" + val PROCESSES_SCREEN = "Processes Screen" + val APPLICATIONS_SCREEN = "Applications Screen" + val TEMPERATURE_SCREEN = "Temperature Screen" + val SETTINGS_SCREEN = "Settings Screen" +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsManager.kt b/app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsManager.kt new file mode 100644 index 00000000..68e2aee7 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/analytics/AnalyticsManager.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.analytics + +import android.app.Application +import android.content.pm.ApplicationInfo +import com.google.android.gms.analytics.GoogleAnalytics +import com.google.android.gms.analytics.HitBuilders +import com.google.android.gms.analytics.Tracker +import com.kgurgul.cpuinfo.R +import timber.log.Timber + +/** + * Implementation of Google Analytics + * + * @author kgurgul + */ +class AnalyticsManager(app: Application) { + + private lateinit var tracker: Tracker + + init { + getDefaultTracker(app) + } + + /** + * Set specific screen name + * + * @param name screen name + */ + fun setScreenName(name: String) { + tracker.setScreenName(name) + tracker.send(HitBuilders.ScreenViewBuilder().build()) + Timber.d("Screen name set to: %s", name) + } + + /** + * Report action from specific category + * + * @param category analytics category + * @param action invoked action + */ + fun reportEvent(category: String, action: String) { + tracker.send(HitBuilders.EventBuilder() + .setCategory(category) + .setAction(action) + .build()) + Timber.d("Report sent. Category: %s, action: %s", category, action) + } + + /** + * Report action from specific category + * + * @param category analytics category + * @param action invoked action + * @param label custom label for specific action + */ + fun reportEvent(category: String, action: String, label: String) { + tracker.send(HitBuilders.EventBuilder() + .setCategory(category) + .setAction(action) + .setLabel(label) + .build()) + Timber.d("Report sent. Category: %s, action: %s, label: %s", category, action, label) + } + + @Synchronized + private fun getDefaultTracker(app: Application) { + val analytics = GoogleAnalytics.getInstance(app) + tracker = analytics.newTracker(R.xml.app_tracker) + Timber.d("Tracker initialized") + val isInDebug = ((app.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0) + if (isInDebug) { + // When dry run is set, hits will not be dispatched, but will still be logged as + // though they were dispatched. + GoogleAnalytics.getInstance(app).setDryRun(true) + Timber.d("Google analytics are disabled for debug") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/common/Prefs.kt b/app/src/main/java/com/kgurgul/cpuinfo/common/Prefs.kt new file mode 100644 index 00000000..310e0062 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/common/Prefs.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.common + +import android.app.Application +import android.content.SharedPreferences +import android.preference.PreferenceManager +import com.google.gson.Gson + +/** + * Simple wrapper for [SharedPreferences] which can also serialize and deserialize object from JSON + */ +class Prefs(var app: Application) { + + fun contains(key: String): Boolean { + return prefs().contains(key) + } + + fun insert(key: String, value: Any) { + when (value) { + is Int -> prefs().edit().putInt(key, value).apply() + is Float -> prefs().edit().putFloat(key, value).apply() + is String -> prefs().edit().putString(key, value).apply() + is Boolean -> prefs().edit().putBoolean(key, value).apply() + is Long -> prefs().edit().putLong(key, value).apply() + else -> { + val s = Gson().toJson(value) + prefs().edit().putString(key, s).apply() + } + } + } + + @Suppress("UNCHECKED_CAST") + fun get(key: String, default: T): T { + when (default) { + is Int -> return prefs().getInt(key, default) as T + is Float -> return prefs().getFloat(key, default) as T + is String -> return prefs().getString(key, default) as T + is Boolean -> return prefs().getBoolean(key, default) as T + is Long -> return prefs().getLong(key, default) as T + else -> { + val value = prefs().getString(key, "") + if (value.isNotEmpty()) { + val typedObject = default as Any + return Gson().fromJson(value, typedObject.javaClass) as T + } + return default + } + } + } + + fun remove(key: String) { + prefs().edit().remove(key).apply() + } + + fun prefs(): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(app) + } +} + diff --git a/app/src/main/java/com/kgurgul/cpuinfo/common/list/AdapterArrayList.kt b/app/src/main/java/com/kgurgul/cpuinfo/common/list/AdapterArrayList.kt new file mode 100644 index 00000000..a4b58ca1 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/common/list/AdapterArrayList.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.common.list + +import android.databinding.ListChangeRegistry +import android.databinding.ObservableList +import java.util.* + +/** + * Copy from [android.databinding.ObservableArrayList] which makes adapters happy with animations. + * It adds [replace] method. + * + * @author kgurgul + */ +class AdapterArrayList : ArrayList(), ObservableList { + + @Transient + private var mListeners: ListChangeRegistry? = null + + override fun addOnListChangedCallback( + listener: ObservableList.OnListChangedCallback>?) { + if (mListeners == null) { + mListeners = ListChangeRegistry() + } + mListeners!!.add(listener) + } + + override fun removeOnListChangedCallback( + listener: ObservableList.OnListChangedCallback>?) { + mListeners?.remove(listener) + } + + override fun add(element: T): Boolean { + super.add(element) + notifyAdd(size - 1, 1) + return true + } + + override fun add(index: Int, element: T) { + super.add(index, element) + notifyAdd(index, 1) + } + + override fun addAll(elements: Collection): Boolean { + val oldSize = size + val added = super.addAll(elements) + if (added) { + notifyAdd(oldSize, size - oldSize) + } + return added + } + + override fun addAll(index: Int, elements: Collection): Boolean { + val added = super.addAll(index, elements) + if (added) { + notifyAdd(index, elements.size) + } + return added + } + + override fun clear() { + val oldSize = size + super.clear() + if (oldSize != 0) { + notifyRemove(0, oldSize) + } + } + + override fun removeAt(index: Int): T { + val value = super.removeAt(index) + notifyRemove(index, 1) + return value + } + + override fun remove(element: T): Boolean { + val index = indexOf(element) + return if (index >= 0) { + removeAt(index) + true + } else { + false + } + } + + override fun set(index: Int, element: T): T { + val value = super.set(index, element) + mListeners?.notifyChanged(this, index, 1) + return value + } + + override fun removeRange(fromIndex: Int, toIndex: Int) { + super.removeRange(fromIndex, toIndex) + notifyRemove(fromIndex, toIndex - fromIndex) + } + + fun replace(elements: Collection) { + super.clear() + super.addAll(elements) + mListeners?.notifyChanged(this) + } + + private fun notifyAdd(start: Int, count: Int) { + mListeners?.notifyInserted(this, start, count) + } + + private fun notifyRemove(start: Int, count: Int) { + mListeners?.notifyRemoved(this, start, count) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/common/list/DividerItemDecoration.kt b/app/src/main/java/com/kgurgul/cpuinfo/common/list/DividerItemDecoration.kt new file mode 100644 index 00000000..be130a3d --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/common/list/DividerItemDecoration.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.common.list + +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.support.v4.content.ContextCompat +import android.support.v7.widget.RecyclerView + +/** + * Customized version of internal [RecyclerView] decorator + * + * @author kgurgul + */ +class DividerItemDecoration : RecyclerView.ItemDecoration { + + companion object { + private val ATTRS = intArrayOf(android.R.attr.listDivider) + } + + private var mDivider: Drawable + + /** + * Default divider will be used + */ + constructor(context: Context) { + val styledAttributes = context.obtainStyledAttributes(ATTRS) + mDivider = styledAttributes.getDrawable(0) + styledAttributes.recycle() + } + + /** + * Custom divider will be used + */ + constructor(context: Context, resId: Int) { + mDivider = ContextCompat.getDrawable(context, resId) + } + + override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) { + val left = parent.paddingLeft + val right = parent.width - parent.paddingRight + + val childCount = parent.childCount + for (i in 0 until childCount) { + val child = parent.getChildAt(i) + + val params = child.layoutParams as RecyclerView.LayoutParams + + val top = child.bottom + params.bottomMargin + val bottom = top + mDivider.intrinsicHeight + + mDivider.setBounds(left, top, right, bottom) + mDivider.draw(c) + } + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/common/list/SimpleListChangeNotifier.kt b/app/src/main/java/com/kgurgul/cpuinfo/common/list/SimpleListChangeNotifier.kt new file mode 100644 index 00000000..27361757 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/common/list/SimpleListChangeNotifier.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.common.list + +import android.databinding.ObservableList +import android.support.v7.widget.RecyclerView +import java.lang.ref.WeakReference + +/** + * In case of any change on the list, this notifier will notify adapter about specific change + * + * @author kgurgul + */ +class SimpleListChangeNotifier(val adapter: WeakReference>) : + ObservableList.OnListChangedCallback>() { + override fun onChanged(sender: ObservableList) { + adapter.get()?.notifyDataSetChanged() + } + + override fun onItemRangeRemoved(sender: ObservableList, positionStart: Int, itemCount: Int) { + adapter.get()?.notifyItemRangeRemoved(positionStart, itemCount) + } + + override fun onItemRangeInserted(sender: ObservableList, positionStart: Int, itemCount: Int) { + adapter.get()?.notifyItemRangeInserted(positionStart, itemCount) + } + + override fun onItemRangeChanged(sender: ObservableList, positionStart: Int, itemCount: Int) { + adapter.get()?.notifyItemRangeChanged(positionStart, itemCount) + } + + override fun onItemRangeMoved(sender: ObservableList, fromPosition: Int, toPosition: Int, itemCount: Int) { + adapter.get()?.notifyDataSetChanged() + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/Annotations.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/Annotations.kt new file mode 100644 index 00000000..6aca5bf6 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/Annotations.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di + +import javax.inject.Scope + +/** + * Dagger scope for Activities + */ +@Scope +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, + AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ActivityScope + +/** + * Dagger scope for Fragments + */ +@Scope +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, + AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.RUNTIME) +annotation class FragmentScope \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/AppInjectior.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/AppInjectior.kt new file mode 100644 index 00000000..11a5fc6a --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/AppInjectior.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentActivity +import android.support.v4.app.FragmentManager +import com.kgurgul.cpuinfo.CpuInfoApp +import com.kgurgul.cpuinfo.di.components.DaggerAppComponent +import dagger.android.AndroidInjection +import dagger.android.support.AndroidSupportInjection +import dagger.android.support.HasSupportFragmentInjector + +/** + * Helper class to automatically inject all dependencies into activities and fragments. + * In case of fragments dependencies are available after onCreate + * + * Taken from https://github.com/googlesamples/android-architecture-components + */ +object AppInjector { + fun init(cpuInfoApp: CpuInfoApp) { + DaggerAppComponent.builder().application(cpuInfoApp).build().inject(cpuInfoApp) + cpuInfoApp + .registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + handleActivity(activity) + } + + override fun onActivityPaused(activity: Activity?) { + } + + override fun onActivityResumed(activity: Activity?) { + } + + override fun onActivityStarted(activity: Activity?) { + } + + override fun onActivityDestroyed(activity: Activity?) { + } + + override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { + } + + override fun onActivityStopped(activity: Activity?) { + } + }) + } + + private fun handleActivity(activity: Activity) { + if (activity is HasSupportFragmentInjector) { + AndroidInjection.inject(activity) + } + (activity as? FragmentActivity)?.supportFragmentManager?.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentPreCreated(fm: FragmentManager, f: Fragment, + savedInstanceState: Bundle?) { + if (f is Injectable) { + AndroidSupportInjection.inject(f) + } + } + }, true) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/Injectable.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/Injectable.kt new file mode 100644 index 00000000..26ae8a86 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/Injectable.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di + +/** + * Marks an activity / fragment injectable. + * + * @author kgurgul + */ +interface Injectable \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/ViewModelInjectionFactory.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/ViewModelInjectionFactory.kt new file mode 100644 index 00000000..ebca5518 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/ViewModelInjectionFactory.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di + +import android.arch.lifecycle.ViewModel +import android.arch.lifecycle.ViewModelProvider +import javax.inject.Inject + +/** + * Helper class which handle ViewModel factory creation + * + * @author kgurgul + */ +class ViewModelInjectionFactory @Inject constructor( + private val viewModel: dagger.Lazy) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = + viewModel.get() as T +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/components/AppComponent.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/components/AppComponent.kt new file mode 100644 index 00000000..83376d16 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/components/AppComponent.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di.components + +import android.app.Application +import com.kgurgul.cpuinfo.CpuInfoApp +import com.kgurgul.cpuinfo.di.modules.ActivityModule +import com.kgurgul.cpuinfo.di.modules.AppModule +import dagger.BindsInstance +import dagger.Component +import dagger.android.support.AndroidSupportInjectionModule +import javax.inject.Singleton + +/** + * App Component with all app singletons + * + * @author kgurgul + */ +@Singleton +@Component(modules = arrayOf(AndroidSupportInjectionModule::class, AppModule::class, + ActivityModule::class)) +interface AppComponent { + + @Component.Builder + interface Builder { + + @BindsInstance + fun application(application: Application): Builder + + fun build(): AppComponent + } + + fun inject(app: CpuInfoApp) +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/modules/ActivityModule.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/modules/ActivityModule.kt new file mode 100644 index 00000000..c4a832f7 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/modules/ActivityModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di.modules + +import com.kgurgul.cpuinfo.features.HostActivity +import dagger.Module +import dagger.android.ContributesAndroidInjector + +/** + * Prepare all activity injections + * + * @author kgurgul + */ +@Module +abstract class ActivityModule { + + @ContributesAndroidInjector(modules = arrayOf(FragmentBuildersModule::class)) + abstract fun contributeFragmentHostActivity(): HostActivity +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/modules/AppModule.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/modules/AppModule.kt new file mode 100644 index 00000000..90c06444 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/modules/AppModule.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di.modules + +import android.app.ActivityManager +import android.app.Application +import android.content.ContentResolver +import android.content.Context +import android.content.pm.PackageManager +import android.content.res.Resources +import android.hardware.SensorManager +import android.view.WindowManager +import com.kgurgul.cpuinfo.analytics.AnalyticsManager +import com.kgurgul.cpuinfo.common.Prefs +import com.kgurgul.cpuinfo.features.information.hardware.BatteryStatusProvider +import com.kgurgul.cpuinfo.features.temperature.TemperatureFormatter +import com.kgurgul.cpuinfo.features.temperature.TemperatureProvider +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + +/** + * Module which can provide all singletons + * + * @author kgurgul + */ +@Module +class AppModule { + + @Provides + @Singleton + fun provideResources(app: Application): Resources = + app.resources + + @Provides + @Singleton + fun provideActivityManager(app: Application): ActivityManager = + app.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + + + @Provides + @Singleton + fun providePackageManager(app: Application): PackageManager = + app.packageManager + + @Provides + @Singleton + fun provideContentResolver(app: Application): ContentResolver = + app.contentResolver + + @Provides + @Singleton + fun provideWindowManager(app: Application): WindowManager = + app.getSystemService(Context.WINDOW_SERVICE) as WindowManager + + @Provides + @Singleton + fun provideSensorManager(app: Application): SensorManager = + app.getSystemService(Context.SENSOR_SERVICE) as SensorManager + + @Provides + @Singleton + fun providePrefs(app: Application): Prefs = + Prefs(app) + + @Provides + @Singleton + fun provideAnalyticsManager(app: Application): AnalyticsManager = + AnalyticsManager(app) + + @Provides + @Singleton + fun provideTemperatureFormatter(prefs: Prefs): TemperatureFormatter = + TemperatureFormatter(prefs) + + @Provides + @Singleton + fun provideTemperatureProvider(app: Application): TemperatureProvider = + TemperatureProvider(app) + + @Provides + @Singleton + fun provideBatteryStatusProvider(app: Application): BatteryStatusProvider = + BatteryStatusProvider(app) +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/di/modules/FragmentBuildersModule.kt b/app/src/main/java/com/kgurgul/cpuinfo/di/modules/FragmentBuildersModule.kt new file mode 100644 index 00000000..eb68ed2c --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/di/modules/FragmentBuildersModule.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.di.modules + +import com.kgurgul.cpuinfo.features.applications.ApplicationsFragment +import com.kgurgul.cpuinfo.features.information.ContainerInfoFragment +import com.kgurgul.cpuinfo.features.information.android.AndroidInfoFragment +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.cpu.CpuInfoFragment +import com.kgurgul.cpuinfo.features.information.gpu.GpuInfoFragment +import com.kgurgul.cpuinfo.features.information.hardware.HardwareInfoFragment +import com.kgurgul.cpuinfo.features.information.ram.RamInfoFragment +import com.kgurgul.cpuinfo.features.information.screen.ScreenInfoFragment +import com.kgurgul.cpuinfo.features.information.sensors.SensorsInfoFragment +import com.kgurgul.cpuinfo.features.information.storage.StorageInfoFragment +import com.kgurgul.cpuinfo.features.processes.ProcessesFragment +import com.kgurgul.cpuinfo.features.temperature.TemperatureFragment +import com.kgurgul.cpuinfo.features.temperature.TemperatureModule +import dagger.Module +import dagger.android.ContributesAndroidInjector + +/** + * Injector for all fragments and their modules + * + * @author kgurgul + */ +@Module +abstract class FragmentBuildersModule { + + @ContributesAndroidInjector + abstract fun contributeAndroidInfoFragment(): AndroidInfoFragment + + @ContributesAndroidInjector + abstract fun contributeCpuInfoFragment(): CpuInfoFragment + + @ContributesAndroidInjector + abstract fun contributeInfoFragment(): ContainerInfoFragment + + @ContributesAndroidInjector + abstract fun contributeBaseInfoFragment(): BaseRvFragment + + @ContributesAndroidInjector + abstract fun contributeGpuInfoFragment(): GpuInfoFragment + + @ContributesAndroidInjector + abstract fun contributeRamInfoFragment(): RamInfoFragment + + @ContributesAndroidInjector + abstract fun contributeSensorsInfoFragment(): SensorsInfoFragment + + @ContributesAndroidInjector + abstract fun contributeStorageInfoFragment(): StorageInfoFragment + + @ContributesAndroidInjector + abstract fun contributeScreenInfoFragment(): ScreenInfoFragment + + @ContributesAndroidInjector + abstract fun contributeHardwareInfoFragment(): HardwareInfoFragment + + @ContributesAndroidInjector + abstract fun contributeApplicationsFragment(): ApplicationsFragment + + @ContributesAndroidInjector + abstract fun contributeProcessesFragment(): ProcessesFragment + + @ContributesAndroidInjector(modules = arrayOf(TemperatureModule::class)) + abstract fun contributeTemperatureFragment(): TemperatureFragment +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/HostActivity.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/HostActivity.kt new file mode 100644 index 00000000..bb8db5dc --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/HostActivity.kt @@ -0,0 +1,174 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features + +import android.annotation.SuppressLint +import android.databinding.DataBindingUtil +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.support.v4.app.Fragment +import android.support.v7.app.ActionBarDrawerToggle +import android.support.v7.app.AppCompatActivity +import android.view.MenuItem +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.databinding.ActivityHostLayoutBinding +import com.kgurgul.cpuinfo.utils.runOnApiAbove +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.support.HasSupportFragmentInjector +import timber.log.Timber +import javax.inject.Inject + +/** + * Base activity which is a host for whole application. + * + * @author kgurgul + */ +class HostActivity : AppCompatActivity(), HasSupportFragmentInjector { + + private val CURRENT_PAGE_ID_KEY = "CURRENT_PAGE_ID_KEY" + + @Inject + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector + + @Inject + lateinit var navigationController: NavigationController + + private lateinit var binding: ActivityHostLayoutBinding + + private var currentItemId = R.id.hardware + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_host_layout) + setSupportActionBar(binding.toolbar) + binding.navigationView.setNavigationItemSelectedListener { menuItem -> + handleMenuNavigation(menuItem) + } + runOnApiAbove(Build.VERSION_CODES.M, { + // Processes cannot be listed above M + val menu = binding.navigationView.menu + menu.findItem(R.id.processes).isVisible = false + }, {}) + val actionBarDrawerToggle = getDrawerToggle() + binding.drawerLayout.addDrawerListener(actionBarDrawerToggle) + actionBarDrawerToggle.syncState() + + if (savedInstanceState == null) { + navigationController.navigateToInfo() + } else { + currentItemId = savedInstanceState.getInt(CURRENT_PAGE_ID_KEY) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + outState.putInt(CURRENT_PAGE_ID_KEY, currentItemId) + super.onSaveInstanceState(outState) + } + + /** + * Handle all navigation callbacks from NavigationDrawer. It also block possibility to open the + * same fragment twice + */ + private fun handleMenuNavigation(menuItem: MenuItem): Boolean { + binding.drawerLayout.closeDrawers() + + if (currentItemId == menuItem.itemId) { + return true + } + currentItemId = menuItem.itemId + + when (menuItem.itemId) { + R.id.hardware -> { + Handler().postDelayed({ + navigationController.navigateToInfo() + setToolbarTitleAndElevation(getString(R.string.hardware)) + }, 500) + return true + } + R.id.applications -> { + Handler().postDelayed({ + navigationController.navigateToApplications() + setToolbarTitleAndElevation(getString(R.string.applications)) + }, 500) + return true + } + R.id.processes -> { + Handler().postDelayed({ + navigationController.navigateToProcesses() + setToolbarTitleAndElevation(getString(R.string.processes)) + }, 500) + return true + } + R.id.temp -> { + Handler().postDelayed({ + navigationController.navigateToTemperature() + setToolbarTitleAndElevation(getString(R.string.temperature)) + }, 500) + return true + } + R.id.settings -> { + Handler().postDelayed({ + navigationController.navigateToSettings() + setToolbarTitleAndElevation(getString(R.string.settings)) + }, 500) + return true + } + else -> { + Timber.e("Bad nav option") + return true + } + } + } + + override fun onBackPressed() { + super.onBackPressed() + val homeFragment = supportFragmentManager + .findFragmentByTag(NavigationController.HOME_FRAGMENT_TAG) + if (homeFragment != null && homeFragment.isVisible) { + currentItemId = R.id.hardware + setToolbarTitleAndElevation(getString(R.string.hardware)) + } + } + + /** + * Set toolbar title and manage elevation in case of L+ devices and TabLayout + */ + @SuppressLint("NewApi") + private fun setToolbarTitleAndElevation(title: String) { + binding.toolbar.title = title + runOnApiAbove(Build.VERSION_CODES.KITKAT_WATCH, { + if (currentItemId == R.id.hardware) { + binding.toolbar.elevation = 0f + } else { + binding.toolbar.elevation = resources.getDimension(R.dimen.elevation_height) + } + }, {}) + } + + /** + * Simple drawer toggle without extra logic + */ + private fun getDrawerToggle(): ActionBarDrawerToggle { + return ActionBarDrawerToggle(this, binding.drawerLayout, binding.toolbar, + R.string.open_drawer, R.string.close_drawer) + } + + override fun supportFragmentInjector(): AndroidInjector + = dispatchingAndroidInjector +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/NavigationController.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/NavigationController.kt new file mode 100644 index 00000000..985987d2 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/NavigationController.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features + +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.analytics.AnalyticsEvents +import com.kgurgul.cpuinfo.analytics.AnalyticsManager +import com.kgurgul.cpuinfo.features.applications.ApplicationsFragment +import com.kgurgul.cpuinfo.features.information.ContainerInfoFragment +import com.kgurgul.cpuinfo.features.processes.ProcessesFragment +import com.kgurgul.cpuinfo.features.settings.SettingsFragment +import com.kgurgul.cpuinfo.features.temperature.TemperatureFragment +import javax.inject.Inject + +/** + * NavigationController controls whole navigation in [HostActivity] + * + * @author kgurgul + */ +class NavigationController @Inject constructor(hostActivity: HostActivity, + private val analyticsManager: AnalyticsManager) { + + companion object { + val HOME_FRAGMENT_TAG = "HOME_FRAGMENT_TAG" + val SECOND_FRAGMENT_TAG = "SECOND_FRAGMENT_TAG" + } + + private val fragmentManager = hostActivity.supportFragmentManager + private val containerId = R.id.fragment_container + + fun navigateToInfo() { + analyticsManager.setScreenName(AnalyticsEvents.INFO_SCREEN) + val infoFragment = ContainerInfoFragment() + if (fragmentManager.findFragmentByTag(HOME_FRAGMENT_TAG) != null) { + val currentFragment = fragmentManager.findFragmentByTag(SECOND_FRAGMENT_TAG) + if (currentFragment != null) { + fragmentManager.beginTransaction().remove(currentFragment).commitNow() + } + fragmentManager.popBackStackImmediate() + } else { + fragmentManager.beginTransaction() + .replace(containerId, infoFragment, HOME_FRAGMENT_TAG) + .commitAllowingStateLoss() + } + } + + fun navigateToTemperature() { + analyticsManager.setScreenName(AnalyticsEvents.TEMPERATURE_SCREEN) + val temperatureFragment = TemperatureFragment() + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(containerId, temperatureFragment, SECOND_FRAGMENT_TAG) + if (fragmentManager.backStackEntryCount == 0) { + fragmentTransaction.addToBackStack(null) + } + fragmentTransaction.commitAllowingStateLoss() + } + + fun navigateToApplications() { + analyticsManager.setScreenName(AnalyticsEvents.APPLICATIONS_SCREEN) + val applicationsFragment = ApplicationsFragment() + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(containerId, applicationsFragment, SECOND_FRAGMENT_TAG) + if (fragmentManager.backStackEntryCount == 0) { + fragmentTransaction.addToBackStack(null) + } + fragmentTransaction.commitAllowingStateLoss() + } + + fun navigateToProcesses() { + analyticsManager.setScreenName(AnalyticsEvents.PROCESSES_SCREEN) + val processesFragment = ProcessesFragment() + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(containerId, processesFragment, SECOND_FRAGMENT_TAG) + if (fragmentManager.backStackEntryCount == 0) { + fragmentTransaction.addToBackStack(null) + } + fragmentTransaction.commitAllowingStateLoss() + } + + fun navigateToSettings() { + analyticsManager.setScreenName(AnalyticsEvents.SETTINGS_SCREEN) + val settingsFragment = SettingsFragment() + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(containerId, settingsFragment, SECOND_FRAGMENT_TAG) + if (fragmentManager.backStackEntryCount == 0) { + fragmentTransaction.addToBackStack(null) + } + fragmentTransaction.commitAllowingStateLoss() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsAdapter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsAdapter.kt new file mode 100644 index 00000000..65f403ae --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsAdapter.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.applications + +import android.annotation.SuppressLint +import android.arch.lifecycle.Lifecycle +import android.arch.lifecycle.LifecycleObserver +import android.arch.lifecycle.OnLifecycleEvent +import android.content.Context +import android.content.pm.PackageManager +import android.databinding.ObservableList +import android.graphics.drawable.Drawable +import android.os.Build +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.SimpleListChangeNotifier +import com.kgurgul.cpuinfo.utils.Utils +import com.kgurgul.cpuinfo.utils.runOnApiBelow +import com.kgurgul.cpuinfo.widgets.swiperv.SwipeHorizontalMenuLayout +import java.io.File +import java.lang.ref.WeakReference + +/** + * Adapter for application list with sliding items + * + * @author kgurgul + */ +class ApplicationsAdapter(context: Context, + private val appList: ObservableList, + private val appClickListener: ItemClickListener) + : RecyclerView.Adapter(), LifecycleObserver { + + private val packageManager: PackageManager = context.packageManager + private val storageLabel: String = context.getString(R.string.storage_used) + private val calculatingLabel: String = context.getString(R.string.calculating) + + private val simpleListChangeNotifier = + SimpleListChangeNotifier(WeakReference(this)) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ApplicationViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_swipe_app, parent, false) + return ApplicationViewHolder(view) + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ApplicationViewHolder, position: Int) { + val app = appList[position] + + var appIcon: Drawable? = null + try { + appIcon = packageManager.getApplicationIcon(app.packageName) + } catch (e: PackageManager.NameNotFoundException) { + // Do nothing + } + holder.iconIv.setImageDrawable(appIcon) + + holder.nameTv.text = app.name + holder.packageTv.text = app.packageName + + runOnApiBelow(Build.VERSION_CODES.O, { + val size = + if (app.appSize == 0L) calculatingLabel + else Utils.humanReadableByteCount(app.appSize) + holder.storageTv.text = "$storageLabel $size" + }, { + holder.storageTv.visibility = View.GONE + }) + + holder.mainContainer.setOnClickListener { + appClickListener.appOpenClicked(holder.adapterPosition) + } + holder.settingsV.setOnClickListener { + appClickListener.appSettingsClicked(holder.adapterPosition) + } + holder.deleteView.setOnClickListener { + holder.sml.smoothCloseMenu() + appClickListener.appUninstallClicked(holder.adapterPosition) + } + + // Native libs + if (app.nativeLibraryDir != null) { + val fileDir = File(app.nativeLibraryDir) + val list = fileDir.listFiles() + + if (list != null && list.isNotEmpty()) { + holder.nativeButtonIV.visibility = View.VISIBLE + holder.nativeButtonIV.setOnClickListener { + appClickListener.appNativeLibsClicked(fileDir) + } + } else { + holder.nativeButtonIV.visibility = View.GONE + } + } else { + holder.nativeButtonIV.visibility = View.GONE + } + } + + override fun getItemCount(): Int { + return appList.size + } + + class ApplicationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val iconIv: ImageView = itemView.findViewById(R.id.app_icon) + val nameTv: TextView = itemView.findViewById(R.id.app_name) + val packageTv: TextView = itemView.findViewById(R.id.app_package) + val storageTv: TextView = itemView.findViewById(R.id.storage_usage) + val sml: SwipeHorizontalMenuLayout = itemView.findViewById(R.id.sml) + val mainContainer: View = itemView.findViewById(R.id.smContentView) + val settingsV: View = itemView.findViewById(R.id.settings) + val deleteView: View = itemView.findViewById(R.id.delete) + val nativeButtonIV: ImageView = itemView.findViewById(R.id.native_button) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + appList.addOnListChangedCallback(simpleListChangeNotifier) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + appList.removeOnListChangedCallback(simpleListChangeNotifier) + } + + /** + * Interface for communication with activity + */ + interface ItemClickListener { + fun appOpenClicked(position: Int) + fun appSettingsClicked(position: Int) + fun appUninstallClicked(position: Int) + fun appNativeLibsClicked(nativeFile: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsFragment.kt new file mode 100644 index 00000000..0ff183e6 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsFragment.kt @@ -0,0 +1,237 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.applications + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.databinding.DataBindingUtil +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import android.support.design.widget.Snackbar +import android.support.v4.app.Fragment +import android.support.v7.app.AlertDialog +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.SimpleItemAnimator +import android.view.* +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.ListView +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.databinding.FragmentApplicationsBinding +import com.kgurgul.cpuinfo.di.Injectable +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.utils.AutoClearedValue +import com.kgurgul.cpuinfo.utils.Utils +import com.kgurgul.cpuinfo.widgets.swiperv.SwipeMenuRecyclerView +import java.io.File +import javax.inject.Inject + +/** + * Activity for apps list. + * + * @author kgurgul + */ +class ApplicationsFragment : Fragment(), Injectable, ApplicationsAdapter.ItemClickListener { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private lateinit var viewModel: ApplicationsViewModel + private lateinit var binding: AutoClearedValue + private lateinit var applicationsAdapter: AutoClearedValue + + private val uninstallReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + viewModel.refreshApplicationsList() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + viewModel = ViewModelProviders.of(this, viewModelInjectionFactory) + .get(ApplicationsViewModel::class.java) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + binding = AutoClearedValue(this, + DataBindingUtil.inflate(inflater, R.layout.fragment_applications, container, false)) + binding.get().viewModel = viewModel + binding.get().swipeRefreshLayout.setOnRefreshListener { viewModel.refreshApplicationsList() } + binding.get().swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, + R.color.colorPrimaryDark) + initObservables() + setupRecyclerView() + return binding.get().root + } + + override fun onStart() { + super.onStart() + registerUninstallBroadcast() + } + + override fun onStop() { + activity.unregisterReceiver(uninstallReceiver) + super.onStop() + } + + override fun onDestroyView() { + // Fix for buggy SwipeMenuRecyclerView which can leak. More investigation needed. + binding.get().recyclerView.adapter = null + super.onDestroyView() + } + + /** + * Setup for [SwipeMenuRecyclerView] + */ + private fun setupRecyclerView() { + applicationsAdapter = AutoClearedValue(this, + ApplicationsAdapter(context, viewModel.applicationList, this)) + + val rvLayoutManager = LinearLayoutManager(context) + binding.get().recyclerView.layoutManager = rvLayoutManager + + binding.get().recyclerView.adapter = applicationsAdapter.get() + binding.get().recyclerView.addItemDecoration(DividerItemDecoration(context)) + (binding.get().recyclerView.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false + lifecycle.addObserver(applicationsAdapter.get()) + } + + /** + * Register all fields from [ApplicationsViewModel] which should be observed + */ + private fun initObservables() { + viewModel.shouldStartStorageService.observe(this, Observer { + StorageUsageService.startService(context, viewModel.applicationList) + }) + } + + /** + * Register broadcast receiver for uninstalling apps + */ + private fun registerUninstallBroadcast() { + val intentFilter = IntentFilter() + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED) + intentFilter.addDataScheme("package") + activity.registerReceiver(uninstallReceiver, intentFilter) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.apps_menu, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_sorting -> { + viewModel.changeAppsSorting() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + /** + * Try to open clicked app. In case of error show [Snackbar]. + */ + override fun appOpenClicked(position: Int) { + val appInfo = viewModel.applicationList[position] + // Block self opening + if (appInfo.packageName == context.packageName) { + Snackbar.make(binding.get().mainContainer, getString(R.string.cpu_open), + Snackbar.LENGTH_SHORT).show() + return + } + + val intent = context.packageManager.getLaunchIntentForPackage(appInfo.packageName) + if (intent != null) { + try { + startActivity(intent) + } catch (e: Exception) { + Snackbar.make(binding.get().mainContainer, getString(R.string.app_open), + Snackbar.LENGTH_SHORT).show() + } + } else { + Snackbar.make(binding.get().mainContainer, getString(R.string.app_open), + Snackbar.LENGTH_SHORT).show() + } + } + + /** + * Open settings activity for selected app + */ + override fun appSettingsClicked(position: Int) { + val appInfo = viewModel.applicationList[position] + val uri = Uri.fromParts("package", appInfo.packageName, null) + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) + startActivity(intent) + } + + /** + * Try to uninstall selected app + */ + override fun appUninstallClicked(position: Int) { + val appInfo = viewModel.applicationList[position] + if (appInfo.packageName == context.packageName) { + Snackbar.make(binding.get().mainContainer, getString(R.string.cpu_uninstall), + Snackbar.LENGTH_SHORT).show() + return + } + + val uri = Uri.fromParts("package", appInfo.packageName, null) + val uninstallIntent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri) + startActivity(uninstallIntent) + } + + /** + * Open dialog with native lib list and open google if user taps on it + */ + override fun appNativeLibsClicked(nativeFile: File) { + showNativeListDialog(nativeFile) + } + + /** + * Create dialog with native libraries list + */ + private fun showNativeListDialog(nativeLibsDir: File) { + val builder = AlertDialog.Builder(context, R.style.CustomAppCompatAlertDialogStyle) + val inflater = LayoutInflater.from(context) + val dialogLayout = inflater.inflate(R.layout.dialog_native_libs, null) + val libs = nativeLibsDir.listFiles().map { it.name } + + val listView: ListView = dialogLayout.findViewById(R.id.dialog_lv) + val arrayAdapter = ArrayAdapter(context, R.layout.item_native_libs, + R.id.native_name_tv, libs) + listView.adapter = arrayAdapter + listView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> + Utils.searchInGoogle(context, libs[position]) + } + builder.setPositiveButton(R.string.ok) { dialog, _ -> + dialog.cancel() + } + builder.setView(dialogLayout) + val alert = builder.create() + alert.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsViewModel.kt new file mode 100644 index 00000000..518dbdee --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ApplicationsViewModel.kt @@ -0,0 +1,170 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.applications + +import android.arch.lifecycle.ViewModel +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.databinding.ObservableBoolean +import android.os.Build +import com.kgurgul.cpuinfo.common.Prefs +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import com.kgurgul.cpuinfo.utils.SingleLiveEvent +import com.kgurgul.cpuinfo.utils.runOnApiBelow +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.experimental.android.UI +import kotlinx.coroutines.experimental.async +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.jetbrains.anko.coroutines.experimental.asReference +import org.jetbrains.anko.coroutines.experimental.bg +import javax.inject.Inject + +/** + * ViewModel for [ApplicationsFragment] + * + * @author kgurgul + */ +class ApplicationsViewModel @Inject constructor( + private val prefs: Prefs, + private val packageManager: PackageManager) : ViewModel() { + + private val SORTING_APPS_KEY = "SORTING_APPS_KEY" + + val isLoading = ObservableBoolean(false) + val applicationList = AdapterArrayList() + val shouldStartStorageService = SingleLiveEvent() + + private val ref = asReference() + private var isSortingAsc = prefs.get(SORTING_APPS_KEY, true) + private var refreshingDisposable: Disposable? = null + + init { + refreshApplicationsList() + EventBus.getDefault().register(this) + } + + @Synchronized + fun refreshApplicationsList() { + if (refreshingDisposable == null || !isLoading.get()) { + refreshingDisposable = getApplicationsListSingle() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe({ _ -> isLoading.set(true) }) + .doFinally({ isLoading.set(false) }) + .subscribe({ appList -> + applicationList.replace(appList) + runOnApiBelow(Build.VERSION_CODES.O, { + shouldStartStorageService.call() + }, {}) + }) + } + } + + /** + * Get all user applications + */ + private fun getApplicationsListSingle(): Single> { + return Single.fromCallable({ + val extendedAppList = ArrayList() + try { + var appsList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA) + .filter { (it.flags and ApplicationInfo.FLAG_SYSTEM) == 0 } + appsList = if (isSortingAsc) { + appsList.sortedBy { + it.loadLabel(packageManager).toString().toUpperCase() + } + } else { + appsList.sortedByDescending { + it.loadLabel(packageManager).toString().toUpperCase() + } + } + appsList.map { + extendedAppList.add( + ExtendedAppInfo(it.loadLabel(packageManager).toString(), + it.packageName, it.nativeLibraryDir)) + } + } catch (e: Exception) { + // Do nothing + } + extendedAppList + }) + } + + /** + * Change apps list sorting type from ascending to descending or or vice versa + */ + fun changeAppsSorting() { + async(UI) { + val result = bg { getAppSortedList() } + val sortedAppList = result.await() + ref().applicationList.replace(sortedAppList) + } + } + + /** + * @return sorted list of the apps from [applicationList] + */ + private fun getAppSortedList(): List { + val appListCopy = ArrayList(applicationList) + isSortingAsc = !isSortingAsc + prefs.insert(SORTING_APPS_KEY, isSortingAsc) + if (isSortingAsc) { + appListCopy.sortBy { it.name.toUpperCase() } + } else { + appListCopy.sortByDescending { it.name.toUpperCase() } + } + return appListCopy + } + + /** + * Update package size whit specific package name using coroutine + */ + @Subscribe + fun onUpdatePackageSizeEvent(event: StorageUsageService.UpdatePackageSizeEvent) { + async(UI) { + val result = bg { getUpdatedApp(event) } + val newAppPair = result.await() + if (newAppPair.first != -1) { + ref().applicationList[newAppPair.first] = newAppPair.second!! + } + } + } + + /** + * @return updated [ExtendedAppInfo] instance with corresponding index + */ + private fun getUpdatedApp(event: StorageUsageService.UpdatePackageSizeEvent) + : Pair { + val app = applicationList.find { it.packageName == event.packageName } + if (app != null) { + val index = applicationList.indexOf(app) + app.appSize = event.size + return Pair(index, app) + } + return Pair(-1, null) + } + + override fun onCleared() { + super.onCleared() + refreshingDisposable?.dispose() + EventBus.getDefault().unregister(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ExtendedAppInfo.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ExtendedAppInfo.kt new file mode 100644 index 00000000..2a6f4fab --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/ExtendedAppInfo.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.applications + +import android.os.Parcel +import android.os.Parcelable + +/** + * Model for [ApplicationsAdapter] + * + * @author kgurgul + */ +data class ExtendedAppInfo(val name: String, + val packageName: String, + val nativeLibraryDir: String?, + var appSize: Long = 0) : Parcelable { + constructor(source: Parcel) : this( + source.readString(), + source.readString(), + source.readString(), + source.readLong() + ) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { + writeString(name) + writeString(packageName) + writeString(nativeLibraryDir) + writeLong(appSize) + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): ExtendedAppInfo = ExtendedAppInfo(source) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/applications/StorageUsageService.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/StorageUsageService.kt new file mode 100644 index 00000000..c7742673 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/applications/StorageUsageService.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2017 KG Soft + * + * 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:Suppress("DEPRECATION") + +package com.kgurgul.cpuinfo.features.applications + +import android.app.IntentService +import android.content.Context +import android.content.Intent +import android.content.pm.IPackageStatsObserver +import android.content.pm.PackageManager +import android.content.pm.PackageStats +import android.os.Bundle +import org.greenrobot.eventbus.EventBus +import timber.log.Timber +import java.util.* + +/** + * Get all storage which is used by passed applications. DON'T work on Android O. + * + * @author kgurgul + */ +class StorageUsageService : IntentService("StorageUsageService") { + + companion object { + val PACKAGES_LIST_TAG = "packages_list_tag" + + fun startService(context: Context, packagesList: ArrayList) { + val intent = Intent(context, StorageUsageService::class.java) + val bundle = Bundle() + bundle.putParcelableArrayList(PACKAGES_LIST_TAG, packagesList) + intent.putExtras(bundle) + context.startService(intent) + } + } + + override fun onHandleIntent(intent: Intent) { + val packagesList = intent.extras.getParcelableArrayList(PACKAGES_LIST_TAG) + + packagesList.forEach { + getPackageSize(it.packageName, packageManager) + } + } + + /** + * Use reflection to get package size + */ + private fun getPackageSize(packageName: String, pm: PackageManager) { + try { + val getPackageSizeInfo = pm.javaClass.getMethod("getPackageSizeInfo", + String::class.java, IPackageStatsObserver::class.java) + getPackageSizeInfo.invoke(pm, packageName, object : IPackageStatsObserver.Stub() { + override fun onGetStatsCompleted(pStats: PackageStats, succeeded: Boolean) { + sendSizeEvent(pStats) + } + }) + } catch (e: Exception) { + Timber.d("Cannot get package size for: $packageName") + } + } + + @Synchronized + fun sendSizeEvent(pStats: PackageStats) { + val size = pStats.codeSize + pStats.dataSize + pStats.cacheSize + Timber.d("Size for: $packageName - $size") + EventBus.getDefault().post(UpdatePackageSizeEvent(pStats.packageName, size)) + } + + data class UpdatePackageSizeEvent(val packageName: String, val size: Long) +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/ContainerInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ContainerInfoFragment.kt new file mode 100644 index 00000000..c8af6c01 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ContainerInfoFragment.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information + +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.databinding.FragmentInfoBinding +import com.kgurgul.cpuinfo.di.Injectable +import com.kgurgul.cpuinfo.features.information.base.ViewPagerAdapter +import com.kgurgul.cpuinfo.utils.AutoClearedValue + +/** + * Fragment which is base for all hardware and software information fragments + * + * @author kgurgul + */ +class ContainerInfoFragment : Fragment(), Injectable { + + private lateinit var binding: AutoClearedValue + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + binding = AutoClearedValue(this, + DataBindingUtil.inflate(inflater, R.layout.fragment_info, container, false)) + return binding.get().root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + val viewPagerAdapter = ViewPagerAdapter(context, childFragmentManager) + binding.get().viewPager.adapter = viewPagerAdapter + binding.get().tabs.setupWithViewPager(binding.get().viewPager) + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoFragment.kt new file mode 100644 index 00000000..fe2751ba --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoFragment.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.android + +import android.arch.lifecycle.ViewModelProviders +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import javax.inject.Inject + +/** + * Fragment for android OS data + * + * @author kgurgul + */ +class AndroidInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private val viewModel: AndroidInfoViewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(AndroidInfoViewModel::class.java) + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoViewModel.kt new file mode 100644 index 00000000..7e584206 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/android/AndroidInfoViewModel.kt @@ -0,0 +1,158 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.android + +import android.annotation.SuppressLint +import android.arch.lifecycle.ViewModel +import android.content.ContentResolver +import android.content.res.Resources +import android.os.Build +import android.provider.Settings +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.security.Security +import java.util.* +import javax.inject.Inject + +/** + * ViewModel for Android OS info + * + * @author kgurgul + */ +class AndroidInfoViewModel @Inject constructor( + private val resources: Resources, + private val contentResolver: ContentResolver) : ViewModel() { + + val dataObservableList = AdapterArrayList>() + + init { + getData() + } + + /** + * Get all data connected with Android OS + */ + private fun getData() { + if (dataObservableList.isNotEmpty()) { + return + } + + dataObservableList.add(Pair(resources.getString(R.string.version), Build.VERSION.RELEASE)) + dataObservableList.add(Pair("SDK", Build.VERSION.SDK_INT.toString())) + dataObservableList.add(Pair(resources.getString(R.string.codename), Build.VERSION.CODENAME)) + dataObservableList.add(Pair("Bootloader", Build.BOOTLOADER)) + dataObservableList.add(Pair(resources.getString(R.string.brand), Build.BRAND)) + dataObservableList.add(Pair(resources.getString(R.string.model), Build.MODEL)) + dataObservableList.add(Pair(resources.getString(R.string.manufacturer), Build.MANUFACTURER)) + dataObservableList.add(Pair(resources.getString(R.string.board), Build.BOARD)) + dataObservableList.add(Pair("VM", getVmVersion())) + dataObservableList.add(Pair("Kernel", System.getProperty("os.version") ?: "")) + @Suppress("DEPRECATION") + dataObservableList.add(Pair(resources.getString(R.string.serial), Build.SERIAL)) + + val androidId = getAndroidId() + if (androidId != null) { + dataObservableList.add(Pair("Android ID", androidId)) + } + + val rooted = isDeviceRooted() + val root = if (rooted) resources.getString(R.string.yes) + else resources.getString(R.string.no) + dataObservableList.add(Pair(resources.getString(R.string.rooted), root)) + + // Security category + val securityProviders = getSecurityProviders() + if (!securityProviders.isEmpty()) { + dataObservableList.add(Pair(resources.getString(R.string.security_providers), "")) + dataObservableList.addAll(securityProviders) + } + } + + /** + * Check if device is rooted. Source: + * https://stackoverflow.com/questions/1101380/determine-if-running-on-a-rooted-device + */ + private fun isDeviceRooted(): Boolean { + return checkRootMethod1() || checkRootMethod2() || checkRootMethod3() + } + + private fun checkRootMethod1(): Boolean { + val buildTags = Build.TAGS + return buildTags != null && buildTags.contains("test-keys") + } + + private fun checkRootMethod2(): Boolean { + val paths = arrayOf( + "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", + "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", + "/system/bin/failsafe/su", "/data/local/su") + + return paths.any { File(it).exists() } + } + + private fun checkRootMethod3(): Boolean { + var process: Process? = null + try { + process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su")) + val br = BufferedReader(InputStreamReader(process.inputStream)) + if (br.readLine() != null) return true + return false + } catch (t: Throwable) { + return false + } finally { + process?.destroy() + } + } + + /** + * Get AndroidID. Keep in mind that from Android O it is unique per app. + */ + @SuppressLint("HardwareIds") + private fun getAndroidId(): String? { + return Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) + } + + /** + * Specify if device is using ART or Dalvik + */ + private fun getVmVersion(): String { + var vm = "Dalvik" + val vmVersion = System.getProperty("java.vm.version") + if (vmVersion != null && vmVersion.startsWith("2")) { + vm = "ART" + } + + return vm + } + + /** + * Get all security providers + */ + private fun getSecurityProviders(): ArrayList> { + val functionsList = ArrayList>() + + val providersList = Security.getProviders() + providersList.forEach { + functionsList.add(Pair(it.name, it.version.toString())) + } + + return functionsList + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/BaseRvFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/BaseRvFragment.kt new file mode 100644 index 00000000..8cacff9c --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/BaseRvFragment.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.base + +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.SimpleItemAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.di.Injectable + +/** + * Simple base for all info fragments which displays data on [RecyclerView] + * + * @author kgurgul + */ +abstract class BaseRvFragment : Fragment(), Injectable { + + protected lateinit var recyclerView: RecyclerView + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.fragment_rv, container, false) + recyclerView = view.findViewById(R.id.recycle_view) + setupRecyclerView() + setupRecyclerViewAdapter() + return view + } + + /** + * Basic [RecyclerView] configuration with [LinearLayoutManager] and disabled animations + */ + private fun setupRecyclerView() { + val layoutManager = LinearLayoutManager(context) + recyclerView.layoutManager = layoutManager + + // Remove change animation + (recyclerView.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false + } + + /** + * Setup adapter for [RecyclerView]. Use this method to set adapter and refreshing logic. + */ + abstract fun setupRecyclerViewAdapter() +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/InfoItemsAdapter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/InfoItemsAdapter.kt new file mode 100644 index 00000000..a3aaf4ae --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/InfoItemsAdapter.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.base + +import android.arch.lifecycle.Lifecycle +import android.arch.lifecycle.LifecycleObserver +import android.arch.lifecycle.OnLifecycleEvent +import android.content.Context +import android.databinding.ObservableList +import android.support.v4.content.ContextCompat +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.SimpleListChangeNotifier +import java.lang.ref.WeakReference + +/** + * Adapter for all info items inside [BaseRvFragment]. It should support two types of layouts: + * vertical and horizontal. It also change color of the first string from passed [Pair] to red if + * the second one is empty. + * + * @author kgurgul + */ +class InfoItemsAdapter(private val context: Context, + private val itemsObservableList: ObservableList>, + private val layoutType: LayoutType = InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) : + RecyclerView.Adapter(), LifecycleObserver { + + enum class LayoutType { + HORIZONTAL_LAYOUT, VERTICAL_LAYOUT + } + + private val simpleListChangeNotifier = + SimpleListChangeNotifier>(WeakReference(this)) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SingleItemViewHolder { + val layout = if (layoutType == LayoutType.HORIZONTAL_LAYOUT) + R.layout.item_value else R.layout.item_value_vertical + val v = LayoutInflater.from(parent.context).inflate(layout, parent, false) + return SingleItemViewHolder(v) + } + + override fun onBindViewHolder(holder: SingleItemViewHolder, position: Int) { + val item = itemsObservableList[position] + holder.titleTv.text = item.first + holder.valueTv.text = item.second + + if (item.second.isEmpty()) { + holder.titleTv.setTextColor(ContextCompat.getColor(context, R.color.colorAccent)) + holder.valueTv.visibility = View.GONE + } else { + holder.titleTv.setTextColor(ContextCompat.getColor(context, R.color.textColorSecondary)) + holder.valueTv.visibility = View.VISIBLE + } + } + + override fun getItemCount(): Int { + return itemsObservableList.size + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + itemsObservableList.addOnListChangedCallback(simpleListChangeNotifier) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + itemsObservableList.removeOnListChangedCallback(simpleListChangeNotifier) + } + + class SingleItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val titleTv: TextView = itemView.findViewById(R.id.title_tv) + val valueTv: TextView = itemView.findViewById(R.id.value_tv) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/ViewPagerAdapter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/ViewPagerAdapter.kt new file mode 100644 index 00000000..a31ee74d --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/base/ViewPagerAdapter.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.base + +import android.content.Context +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v4.app.FragmentPagerAdapter +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.features.information.android.AndroidInfoFragment +import com.kgurgul.cpuinfo.features.information.cpu.CpuInfoFragment +import com.kgurgul.cpuinfo.features.information.gpu.GpuInfoFragment +import com.kgurgul.cpuinfo.features.information.hardware.HardwareInfoFragment +import com.kgurgul.cpuinfo.features.information.ram.RamInfoFragment +import com.kgurgul.cpuinfo.features.information.screen.ScreenInfoFragment +import com.kgurgul.cpuinfo.features.information.sensors.SensorsInfoFragment +import com.kgurgul.cpuinfo.features.information.storage.StorageInfoFragment + +/** + * Simple view pager for info fragments + */ +class ViewPagerAdapter(val context: Context, manager: FragmentManager) : + FragmentPagerAdapter(manager) { + + override fun getItem(position: Int): Fragment { + return when (position) { + 0 -> CpuInfoFragment() + 1 -> GpuInfoFragment() + 2 -> RamInfoFragment() + 3 -> StorageInfoFragment() + 4 -> ScreenInfoFragment() + 5 -> AndroidInfoFragment() + 6 -> HardwareInfoFragment() + 7 -> SensorsInfoFragment() + else -> throw IllegalArgumentException("Unknown position for ViewPager") + } + } + + override fun getCount(): Int { + return 8 + } + + override fun getPageTitle(position: Int): CharSequence { + return when (position) { + 0 -> context.getString(R.string.cpu) + 1 -> context.getString(R.string.gpu) + 2 -> context.getString(R.string.ram) + 3 -> context.getString(R.string.storage) + 4 -> context.getString(R.string.screen) + 5 -> context.getString(R.string.android) + 6 -> context.getString(R.string.hardware) + 7 -> context.getString(R.string.sensors) + else -> throw IllegalArgumentException("Unknown position for ViewPager") + } + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoFragment.kt new file mode 100644 index 00000000..c28119d9 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoFragment.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.cpu + +import android.arch.lifecycle.ViewModelProviders +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import javax.inject.Inject + +/** + * Displays information about device CPU taken form /proc/cpuinfo file + * + * @author kgurgul + */ +class CpuInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private val viewModel: CpuInfoViewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(CpuInfoViewModel::class.java) + } + + override fun onStart() { + super.onStart() + viewModel.startProvidingData() + } + + override fun onStop() { + viewModel.stopProvidingData() + super.onStop() + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoViewModel.kt new file mode 100644 index 00000000..6e449d2d --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/cpu/CpuInfoViewModel.kt @@ -0,0 +1,238 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.cpu + +import android.arch.lifecycle.ViewModel +import android.os.Build +import android.support.annotation.UiThread +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import io.reactivex.Flowable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import timber.log.Timber +import java.io.* +import java.util.ArrayList +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern +import javax.inject.Inject +import kotlin.collections.HashMap + +/** + * ViewModel for CPU information + * + * @author kgurgul + */ +class CpuInfoViewModel @Inject constructor() : ViewModel() { + + private val REFRESHING_INTERVAL = 1L + private val FREQUENCY_OFFSET = 1 + private val excludedTags = listOf("processor", "BogoMIPS") + private val minMaxFreqMap = HashMap>() + private var refreshingDisposable: Disposable? = null + + val dataObservableList = AdapterArrayList>() + + @Synchronized + fun startProvidingData() { + if (dataObservableList.isEmpty()) { + val cpuInfoList = ArrayList>() + cpuInfoList.add(getAbi()) + cpuInfoList.addAll(getCoresInfo()) + cpuInfoList.addAll(getCpuInfoFromFile()) + dataObservableList.addAll(cpuInfoList) + } + + if (refreshingDisposable == null || refreshingDisposable?.isDisposed != false) { + refreshingDisposable = Flowable.interval(REFRESHING_INTERVAL, TimeUnit.SECONDS) + .onBackpressureDrop() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _ -> refreshFrequencies() }, + { e -> Timber.e(e) }) + } + } + + fun stopProvidingData() { + refreshingDisposable?.dispose() + } + + /** + * Get current CPU frequency and refresh [dataObservableList] + */ + @UiThread + private fun refreshFrequencies() { + Timber.i("refreshFrequencies()") + val freqList = getCoresInfo() + freqList.forEachIndexed { i, pair -> dataObservableList[i + FREQUENCY_OFFSET] = pair } + } + + /** + * Get current ABI + */ + private fun getAbi(): Pair { + val abi: String + if (Build.VERSION.SDK_INT >= 21) { + abi = Build.SUPPORTED_ABIS[0] + } else { + @Suppress("DEPRECATION") + abi = Build.CPU_ABI + } + return Pair("ABI", abi) + } + + /** + * Get all information about CPU cores amount and theirs frequencies + */ + private fun getCoresInfo(): ArrayList> { + val coresList = ArrayList>() + val numbersOfCores = getNumberOfCores() + var i = 0 + coresList.add(Pair("Cores", numbersOfCores.toString())) + while (i < numbersOfCores) { + coresList.add(Pair(" Core $i", tryToGetCurrentFreq(i))) + i++ + } + return coresList + } + + /** + * Get global info for cpu from /proc/cpuinfo. Ignore cores because of buggy value on some + * devices. + */ + private fun getCpuInfoFromFile(): ArrayList> { + val featuresList = ArrayList>() + + val cpuInfoFile = File("/proc/cpuinfo") + if (cpuInfoFile.exists()) { + try { + val br = BufferedReader(FileReader(cpuInfoFile)) + var aLine: String? + + aLine = br.readLine() + while (aLine != null) { + var stringParts = aLine.split(":") + if (stringParts.size == 2) { + stringParts = stringParts.map { it.trim() } + + if (!excludedTags.contains(stringParts[0])) { + featuresList.add(Pair(stringParts[0].capitalize(), stringParts[1])) + } + } + aLine = br.readLine() + } + + br.close() + } catch (e: IOException) { + Timber.e(e) + } + } + return featuresList + } + + /** + * Checking frequencies directories and return current value if exists (otherwise we can + * assume that core is stopped) + */ + private fun tryToGetCurrentFreq(coreNumber: Int): String { + var frequency = "Stopped" + val filePath = "/sys/devices/system/cpu/cpu$coreNumber/cpufreq/scaling_cur_freq" + + try { + val reader = RandomAccessFile(filePath, "r") + val value = reader.readLine().toLong() / 1000 + reader.close() + frequency = "${value}MHz" + + if (minMaxFreqMap.containsKey(coreNumber)) { + frequency += "\n(${minMaxFreqMap[coreNumber]?.first}MHz - ${minMaxFreqMap[coreNumber]?.second}MHz)" + } else { + val minMaxPair = tryToGetMinMaxFreq(coreNumber) + if (minMaxPair != null) { + frequency += "\n(${minMaxPair.first}MHz - ${minMaxPair.second}MHz)" + } + } + } catch (e: Exception) { + // Timber.e(e) + } + + return frequency + } + + /** + * Read max/min frequencies for all cores + */ + private fun tryToGetMinMaxFreq(coreNumber: Int): Pair? { + val minPath = "/sys/devices/system/cpu/cpu$coreNumber/cpufreq/cpuinfo_min_freq" + val maxPath = "/sys/devices/system/cpu/cpu$coreNumber/cpufreq/cpuinfo_max_freq" + try { + var reader = RandomAccessFile(minPath, "r") + val minMhz = reader.readLine().toLong() / 1000 + reader.close() + + reader = RandomAccessFile(maxPath, "r") + val maxMhz = reader.readLine().toLong() / 1000 + reader.close() + val values = Pair(minMhz, maxMhz) + minMaxFreqMap.put(coreNumber, values) + return values + } catch (e: Exception) { + Timber.e(e) + } + return null + } + + /** + * Get number of CPU cores + */ + private fun getNumberOfCores(): Int { + return if (Build.VERSION.SDK_INT >= 17) { + Runtime.getRuntime().availableProcessors() + } else { + getNumCoresOldPhones() + } + } + + /** + * Gets the number of cores available in this device, across all processors. + * Requires: Ability to peruse the filesystem at "/sys/devices/system/cpu" + * + * @return The number of cores, or 1 if failed to get result + */ + private fun getNumCoresOldPhones(): Int { + // Private Class to display only CPU devices in the directory listing + class CpuFilter : FileFilter { + override fun accept(pathname: File): Boolean { + // Check if filename is "cpu", followed by a single digit number + if (Pattern.matches("cpu[0-9]+", pathname.name)) { + return true + } + return false + } + } + + return try { + // Get directory containing CPU info + val dir = File("/sys/devices/system/cpu/") + // Filter to only list the devices we care about + val files = dir.listFiles(CpuFilter()) + // Return the number of cores (virtual CPU devices) + files.size + } catch(e: Exception) { + // Default is 1 core + 1 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoFragment.kt new file mode 100644 index 00000000..0b7fa244 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoFragment.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.gpu + +import android.arch.lifecycle.ViewModelProviders +import android.opengl.GLSurfaceView +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import com.kgurgul.cpuinfo.utils.Utils +import java.util.* +import javax.inject.Inject +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.opengles.GL10 + +/** + * Fragment which extends [BaseRvFragment] to provide OpenGL info from custom [GLSurfaceView] + * + * @author kgurgul + */ +class GpuInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + private lateinit var viewModel: GpuInfoViewModel + + private val gpuInfoList = ArrayList>() + private var glSurfaceView: GLSurfaceView? = null + private lateinit var handler: Handler + private val glRenderer = object : GLSurfaceView.Renderer { + + override fun onSurfaceCreated(gl: GL10, config: EGLConfig) { + Utils.addPairIfExists(gpuInfoList, getString(R.string.vendor), gl.glGetString(GL10.GL_VENDOR)) + Utils.addPairIfExists(gpuInfoList, getString(R.string.version), gl.glGetString(GL10.GL_VERSION)) + Utils.addPairIfExists(gpuInfoList, getString(R.string.renderer), gl.glGetString(GL10.GL_RENDERER)) + Utils.addPairIfExists(gpuInfoList, getString(R.string.extensions), gl.glGetString(GL10.GL_EXTENSIONS)) + + handler.sendEmptyMessage(0) + } + + override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { + } + + override fun onDrawFrame(gl: GL10) { + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(this, viewModelInjectionFactory) + .get(GpuInfoViewModel::class.java) + handler = object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message?) { + glSurfaceView?.visibility = View.GONE + viewModel.addGlInfo(gpuInfoList) + } + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view = super.onCreateView(inflater, container, savedInstanceState) + + if (!viewModel.isGlInfoStored()) { + glSurfaceView = GLSurfaceView(activity) + glSurfaceView?.setRenderer(glRenderer) + + val mainContainer: ViewGroup = view.findViewById(R.id.main_container) + mainContainer.addView(glSurfaceView) + } + return view + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoViewModel.kt new file mode 100644 index 00000000..a03b2736 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/gpu/GpuInfoViewModel.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.gpu + +import android.app.ActivityManager +import android.arch.lifecycle.ViewModel +import android.content.res.Resources +import android.text.TextUtils +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import javax.inject.Inject + +/** + * ViewModel for GPU information. It is using custom SurfaceView to get more GPU details from OpenGL + * + * @author kgurgul + */ +class GpuInfoViewModel @Inject constructor(private val activityManager: ActivityManager, + private val resources: Resources) : ViewModel() { + + val dataObservableList = AdapterArrayList>() + + init { + getGpuData() + } + + /** + * Get all GPU information + */ + private fun getGpuData() { + if (dataObservableList.isEmpty()) { + val configurationInfo = activityManager.deviceConfigurationInfo + val version = configurationInfo.glEsVersion + if (!TextUtils.isEmpty(version)) { + // Add GLES version on the first position because this ViewModel doesn't contains + // synchronization with "addGlInfo" method + dataObservableList.add(0, Pair(resources.getString(R.string.gles_version), + version)) + } + } + } + + /** + * Check if additional GPU info was already added into [dataObservableList] + */ + fun isGlInfoStored(): Boolean { + return dataObservableList.size > 1 + } + + /** + * Add additional GPU info from OpenGL if it wasn't added previously + * + * @param gpuInfoList list of additional data like version, render etc. + */ + fun addGlInfo(gpuInfoList: List>) { + if (dataObservableList.size <= 1) { + dataObservableList.addAll(gpuInfoList) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/BatteryStatusProvider.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/BatteryStatusProvider.kt new file mode 100644 index 00000000..3ec753fe --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/BatteryStatusProvider.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.hardware + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import com.kgurgul.cpuinfo.di.FragmentScope +import timber.log.Timber +import javax.inject.Inject + +/** + * Return battery status from ACTION_BATTERY_CHANGED broadcast receiver and capacity from + * com.android.internal.os.PowerProfile + * + * @author kgurgul + */ +@FragmentScope +class BatteryStatusProvider @Inject constructor(private val app: Application) { + + /** + * @return [Intent] with battery information + */ + fun getBatteryStatusIntent(): Intent { + val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + return app.registerReceiver(null, iFilter) + } + + /** + * @return battery capacity from private API. In case of error it will return -1. + */ + @SuppressLint("PrivateApi") + fun getBatteryCapacity(): Double { + var capacity = -1.0 + + try { + val powerProfile = Class.forName("com.android.internal.os.PowerProfile") + .getConstructor(Context::class.java).newInstance(app) + capacity = Class + .forName("com.android.internal.os.PowerProfile") + .getMethod("getAveragePower", String::class.java) + .invoke(powerProfile, "battery.capacity") as Double + } catch (e: Exception) { + Timber.e(e) + } + + return capacity + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoFragment.kt new file mode 100644 index 00000000..c265d0d4 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoFragment.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.hardware + +import android.arch.lifecycle.ViewModelProviders +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import javax.inject.Inject + +/** + * Fragment responsible for hardware info. It also contains [BroadcastReceiver] for AC connection. + * + * @author kgurgul + */ +class HardwareInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private val viewModel: HardwareInfoViewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(HardwareInfoViewModel::class.java) + } + + private val powerReceiver = object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + viewModel.refreshHardwareInfo() + } + } + + override fun onResume() { + super.onResume() + + val intentFilter = IntentFilter() + intentFilter.addAction("android.intent.action.ACTION_POWER_CONNECTED") + intentFilter.addAction("android.intent.action.ACTION_POWER_DISCONNECTED") + + activity.registerReceiver(powerReceiver, intentFilter) + } + + override fun onPause() { + super.onPause() + activity.unregisterReceiver(powerReceiver) + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoViewModel.kt new file mode 100644 index 00000000..e47df1d4 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/hardware/HardwareInfoViewModel.kt @@ -0,0 +1,342 @@ +/* + * Copyright 2017 KG Soft + * + * 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:Suppress("DEPRECATION") + +package com.kgurgul.cpuinfo.features.information.hardware + +import android.arch.lifecycle.ViewModel +import android.content.ContentResolver +import android.content.pm.PackageManager +import android.content.res.Resources +import android.hardware.Camera +import android.os.BatteryManager +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import com.kgurgul.cpuinfo.features.temperature.TemperatureFormatter +import com.kgurgul.cpuinfo.utils.Utils +import com.kgurgul.cpuinfo.utils.round2 +import timber.log.Timber +import java.io.File +import java.io.FileFilter +import java.io.RandomAccessFile +import java.util.* +import java.util.regex.Pattern +import javax.inject.Inject + +/** + * ViewModel for [HardwareInfoFragment] + * + * @author kgurgul + */ +class HardwareInfoViewModel @Inject constructor( + private val resources: Resources, + private val temperatureFormatter: TemperatureFormatter, + private val packageManager: PackageManager, + private val contentResolver: ContentResolver, + private val batteryStatusProvider: BatteryStatusProvider) + : ViewModel() { + + val dataObservableList = AdapterArrayList>() + + init { + refreshHardwareInfo() + } + + /** + * Refresh all info connected with hardware like: battery, wireless connection (Wi-Fi, + * Bluetooth), sound card and camera + */ + @Synchronized + fun refreshHardwareInfo() { + if (dataObservableList.isNotEmpty()) { + dataObservableList.clear() + } + + dataObservableList.add(Pair(resources.getString(R.string.battery), "")) + dataObservableList.addAll(getBatteryStatus()) + + val wirelessInfo = getWirelessInfo() + if (wirelessInfo.size > 0) { + dataObservableList.add(Pair(resources.getString(R.string.wireless), "")) + dataObservableList.addAll(wirelessInfo) + } + + dataObservableList.add(Pair(resources.getString(R.string.sound_card), "")) + dataObservableList.addAll(getSoundCardInfo()) + + // Camera + if (hasCamera()) { + dataObservableList.add(Pair(resources.getString(R.string.cameras), "")) + dataObservableList.addAll(getCameraInfo()) + } + } + + /** + * Collect information about battery + */ + private fun getBatteryStatus(): ArrayList> { + val functionsList = ArrayList>() + + val batteryStatus = batteryStatusProvider.getBatteryStatusIntent() + + // Level + val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + + if (level != -1 && scale != -1) { + val batteryPct = level / scale.toFloat() * 100.0 + functionsList.add(Pair(resources.getString(R.string.level), "${batteryPct.round2()}%")) + } + + // Health + val health = batteryStatus.getIntExtra(BatteryManager.EXTRA_HEALTH, -1) + if (health != -1) { + functionsList.add(Pair(resources.getString(R.string.battery_health), + getBatteryHealthStatus(health))) + } + + // Voltage + val voltage = batteryStatus.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1) + if (voltage > 0) { + functionsList.add(Pair(resources.getString(R.string.voltage), "${voltage / 1000.0}V")) + } + + // Temperature + val temperature = batteryStatus.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1) + if (temperature > 0) { + functionsList.add(Pair(resources.getString(R.string.temperature), + temperatureFormatter.format(temperature / 10.0f))) + } + + // Capacity + val capacity = batteryStatusProvider.getBatteryCapacity().round2() + if (capacity != -1.0) { + functionsList.add(Pair(resources.getString(R.string.capacity), "${capacity}mAh")) + } + + // Technology + val technology = batteryStatus.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY) + Utils.addPairIfExists(functionsList, resources.getString(R.string.technology), technology) + + // Are we charging / is charged? + val status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1) + val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL + + // How we charging? + val chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) + val usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB + val acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC + + val charging = + if (isCharging) resources.getString(R.string.yes) + else resources.getString(R.string.no) + functionsList.add(Pair(resources.getString(R.string.is_charging), charging)) + if (isCharging) { + val chargingType: String = when { + usbCharge -> "USB" + acCharge -> "AC" + else -> resources.getString(R.string.unknown) + } + functionsList.add(Pair(resources.getString(R.string.charging_type), chargingType)) + } + + return functionsList + } + + /** + * @return battery health status as a string + */ + private fun getBatteryHealthStatus(healthInt: Int): String { + return when (healthInt) { + BatteryManager.BATTERY_HEALTH_COLD -> resources.getString(R.string.battery_cold) + BatteryManager.BATTERY_HEALTH_GOOD -> resources.getString(R.string.battery_good) + BatteryManager.BATTERY_HEALTH_DEAD -> resources.getString(R.string.battery_dead) + BatteryManager.BATTERY_HEALTH_OVERHEAT -> resources.getString(R.string.battery_overheat) + BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE -> resources.getString(R.string.battery_overvoltage) + BatteryManager.BATTERY_HEALTH_UNKNOWN -> resources.getString(R.string.battery_unknown) + BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE -> resources.getString(R.string.battery_unspecified_failure) + else -> resources.getString(R.string.battery_unknown) + } + } + + /** + * Get Wi-Fi and Bluetooth mac address + */ + private fun getWirelessInfo(): ArrayList> { + val functionsList = ArrayList>() + + // Wi-Fi mac + val filePath = "/sys/class/net/wlan0/address" + try { + val reader = RandomAccessFile(filePath, "r") + val value = reader.readLine() + reader.close() + functionsList.add(Pair(resources.getString(R.string.wifi_mac), value)) + } catch (e: NumberFormatException) { + e.printStackTrace() + } catch (e: Exception) { + } + + // Bluetooth mac + val bluetoothMac = android.provider.Settings.Secure.getString(contentResolver, + "bluetooth_address") + if (bluetoothMac != null && !bluetoothMac.isEmpty()) + functionsList.add(Pair(resources.getString(R.string.bluetooth_mac), bluetoothMac)) + + return functionsList + } + + /** + * @return number of the sound card in device - default is 1 + */ + private fun getSoundCardNumber(): Int { + class AudioFilter : FileFilter { + override fun accept(pathname: File): Boolean { + // http://alsa.opensrc.org/Proc_asound_documentation + return Pattern.matches("card[0-7]+", pathname.name) + } + } + + return try { + val dir = File("/proc/asound/") + val files = dir.listFiles(AudioFilter()) + files.size + } catch (e: Exception) { + 1 + } + } + + /** + * Get available data connected with sound card like ALSA version etc. + */ + private fun getSoundCardInfo(): ArrayList> { + val functionsList = ArrayList>() + + val soundCardNumber = getSoundCardNumber() + functionsList.add(Pair(resources.getString(R.string.amount), soundCardNumber.toString())) + + var iterator = 0 + while (iterator < soundCardNumber) { + functionsList.add(Pair(" ${resources.getString(R.string.card)} $iterator", + tryToGetSoundCardId(iterator))) + iterator++ + } + + // ALSA + val alsa = tryToGetAlsa() + Utils.addPairIfExists(functionsList, "ALSA", alsa) + + return functionsList + } + + /** + * Try to read id of the sound card. + * + * @param cardPosition position of the sound card in files + * @return id from "id" file + */ + private fun tryToGetSoundCardId(cardPosition: Int): String { + var id = resources.getString(R.string.unknown) + val filePath = "/proc/asound/card$cardPosition/id" + + var reader: RandomAccessFile? = null + try { + reader = RandomAccessFile(filePath, "r") + id = reader.readLine() + } catch (e: Exception) { + // Do nothing + } finally { + reader?.close() + } + + return id + } + + /** + * @return ALSA version if exists, otherwise null + */ + private fun tryToGetAlsa(): String? { + var alsa: String? = null + val filePath = "/proc/asound/version" + + var reader: RandomAccessFile? = null + try { + reader = RandomAccessFile(filePath, "r") + val version = reader.readLine() + alsa = version + } catch (e: Exception) { + // Do nothing + } finally { + reader?.close() + } + + return alsa + } + + /** + * @return true if device has at least 1 camera, otherwise false + */ + private fun hasCamera(): Boolean { + return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) + } + + /** + * Get number, type and orientation of the cameras + */ + @Suppress("DEPRECATION") + private fun getCameraInfo(): ArrayList> { + val functionsList = ArrayList>() + + val numbersOfCameras = Camera.getNumberOfCameras() + functionsList.add(Pair(resources.getString(R.string.amount), numbersOfCameras.toString())) + + val cameraName = resources.getString(R.string.camera) + val cameraType = resources.getString(R.string.type) + val cameraOrientation = resources.getString(R.string.orientation) + var iterator = 0 + while (iterator < numbersOfCameras) { + functionsList.add(Pair(" $cameraName $iterator", " ")) + + // Strange error on some SM-G930F with getCameraInfo + try { + val info = Camera.CameraInfo() + Camera.getCameraInfo(iterator, info) + val type = getCameraType(info) + functionsList.add(Pair(" $cameraType", type)) + functionsList.add(Pair(" $cameraOrientation", info.orientation.toString())) + } catch (e: Exception) { + Timber.e(e) + } + + iterator++ + } + + return functionsList + } + + /** + * Detect camera type using old API + */ + private fun getCameraType(info: Camera.CameraInfo): String { + return when (info.facing) { + Camera.CameraInfo.CAMERA_FACING_FRONT -> resources.getString(R.string.front) + Camera.CameraInfo.CAMERA_FACING_BACK -> resources.getString(R.string.back) + else -> resources.getString(R.string.unknown) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/ClearRamAsyncTask.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/ClearRamAsyncTask.kt new file mode 100644 index 00000000..de2c0c6d --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/ClearRamAsyncTask.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.ram + +import android.app.ActivityManager +import android.app.Application +import android.content.Context +import android.content.pm.ApplicationInfo +import android.os.AsyncTask +import android.os.Build +import com.kgurgul.cpuinfo.CpuInfoApp +import com.kgurgul.cpuinfo.utils.processmanager.AndroidProcesses +import java.util.* + +/** + * Simple task for all Android which reset most popular apps and runs GC + * + * @author kgurgul + */ +class ClearRamAsyncTask : AsyncTask() { + + override fun doInBackground(vararg params: Void?): Void? { + freeRam() + return null + } + + private fun freeRam() { + // GC operations + System.runFinalization() + Runtime.getRuntime().gc() + System.gc() + + // Not my solution - really bad idea but user wants ram :-) + // For SDK 24 we can't get running processes list + if (Build.VERSION.SDK_INT < 24) { + freeMemoryUsingVoodoo(CpuInfoApp.instance) + } + } + + private fun freeMemoryUsingVoodoo(app: Application) { + val reservedPackages = ArrayList() + reservedPackages.add("system") + reservedPackages.add("com.android.launcher2") + reservedPackages.add("com.android.inputmethod.latin") + reservedPackages.add("com.android.phone") + reservedPackages.add("com.android.wallpaper") + reservedPackages.add("com.google.process.gapps") + reservedPackages.add("android.process.acore") + reservedPackages.add("android.process.media") + reservedPackages.add("com.android.bluetooth") + reservedPackages.add(app.packageName) + + val packagesToKill = ArrayList() + val am = app.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + for (process in AndroidProcesses.getRunningAppProcesses()) { + try { + val appInfo = process.getPackageInfo(app, 0).applicationInfo + if (!reservedPackages.contains(process.packageName) + && appInfo.flags and ApplicationInfo.FLAG_PERSISTENT == 0) { + packagesToKill.add(process.packageName) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + for (pck in packagesToKill) { + am.killBackgroundProcesses(pck) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoFragment.kt new file mode 100644 index 00000000..0ef8c44b --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoFragment.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.ram + +import android.arch.lifecycle.ViewModelProviders +import android.os.Build +import android.os.Bundle +import android.support.design.widget.Snackbar +import android.view.* +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import javax.inject.Inject + +/** + * Fragment which contains RAM info. For older android there is also available cleaning option. + * + * @author kgurgul + */ +class RamInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private val viewModel: RamInfoViewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(RamInfoViewModel::class.java) + } + + private lateinit var mainContainer: View + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view = super.onCreateView(inflater, container, savedInstanceState) + mainContainer = view.findViewById(R.id.main_container) + return view + } + + override fun onStart() { + super.onStart() + viewModel.startProvidingData() + } + + override fun onStop() { + viewModel.stopProvidingData() + super.onStop() + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + // Cleaning options works only for old Android + if (Build.VERSION.SDK_INT < 24) { + inflater.inflate(R.menu.ram_menu, menu) + } + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_gc -> { + viewModel.clearRam() + Snackbar.make(mainContainer, getString(R.string.running_gc), Snackbar.LENGTH_SHORT) + .show() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoViewModel.kt new file mode 100644 index 00000000..7eeaf3fc --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/ram/RamInfoViewModel.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.ram + +import android.app.ActivityManager +import android.arch.lifecycle.ViewModel +import android.content.res.Resources +import android.os.AsyncTask +import android.os.Build +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import com.kgurgul.cpuinfo.utils.Utils +import io.reactivex.Flowable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import timber.log.Timber +import java.io.RandomAccessFile +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern +import javax.inject.Inject + +/** + * ViewModel for RAM info + * + * @author kgurgul + */ +class RamInfoViewModel @Inject constructor(private val activityManager: ActivityManager, + private val resources: Resources) : ViewModel() { + + private val REFRESHING_INTERVAL = 5L + private val memoryInfo = ActivityManager.MemoryInfo() + private var ramRefreshingDisposable: Disposable? = null + private var clearRamAsyncTask: ClearRamAsyncTask? = null + + val dataObservableList = AdapterArrayList>() + + @Synchronized + fun startProvidingData() { + if (ramRefreshingDisposable == null || ramRefreshingDisposable!!.isDisposed) { + ramRefreshingDisposable = Flowable.interval(0, REFRESHING_INTERVAL, TimeUnit.SECONDS) + .onBackpressureDrop() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _ -> refreshRamData() }, + { e -> Timber.e(e) }) + } + } + + fun stopProvidingData() { + ramRefreshingDisposable?.dispose() + } + + /** + * Get RAM info: all, available and threshold + */ + private fun refreshRamData() { + Timber.i("refreshRamData()") + val memoryInfoList = ArrayList>() + activityManager.getMemoryInfo(memoryInfo) + + if (Build.VERSION.SDK_INT >= 16) { + val availableMemoryPercent = (memoryInfo.availMem.toDouble() + / memoryInfo.totalMem.toDouble() * 100.0).toInt() + + memoryInfoList.add(Pair(resources.getString(R.string.total_memory), + Utils.convertBytesToMega(memoryInfo.totalMem))) + memoryInfoList.add(Pair(resources.getString(R.string.available_memory), + "${Utils.convertBytesToMega(memoryInfo.availMem)} ($availableMemoryPercent%)")) + } else { + val totalRam = getTotalRamForOldApi() + val availableMemoryPercent = (memoryInfo.availMem.toDouble() + / totalRam.toDouble() * 100.0).toInt() + + memoryInfoList.add(Pair(resources.getString(R.string.total_memory), + Utils.convertBytesToMega(totalRam))) + memoryInfoList.add(Pair(resources.getString(R.string.available_memory), + "${Utils.convertBytesToMega(memoryInfo.availMem)} ($availableMemoryPercent%)")) + } + + memoryInfoList.add(Pair(resources.getString(R.string.threshold), + Utils.humanReadableByteCount(memoryInfo.threshold))) + + dataObservableList.replace(memoryInfoList) + } + + /** + * Legacy method for old Android + */ + private fun getTotalRamForOldApi(): Long { + var reader: RandomAccessFile? = null + var totRam: Long = -1 + try { + reader = RandomAccessFile("/proc/meminfo", "r") + val load = reader.readLine() + + // Get the Number value from the string + val p = Pattern.compile("(\\d+)") + val m = p.matcher(load) + var value = "" + while (m.find()) { + value = m.group(1) + } + reader.close() + + totRam = value.toLong() + } catch (e: Exception) { + Timber.e(e) + } finally { + reader?.close() + } + + return totRam * 1024 // bytes + } + + /** + * Run task to clear RAM memory + */ + fun clearRam() { + if (clearRamAsyncTask == null || clearRamAsyncTask?.status != AsyncTask.Status.RUNNING) { + clearRamAsyncTask?.cancel(true) + clearRamAsyncTask = ClearRamAsyncTask() + clearRamAsyncTask?.execute() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoFragment.kt new file mode 100644 index 00000000..e84acf3a --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoFragment.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.screen + +import android.arch.lifecycle.ViewModelProviders +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import javax.inject.Inject + +/** + * Displays information about device screen resolution, density etc. + * + * @author kgurgul + */ +class ScreenInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private val viewModel: ScreenInfoViewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(ScreenInfoViewModel::class.java) + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.HORIZONTAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoViewModel.kt new file mode 100644 index 00000000..bd09614f --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/screen/ScreenInfoViewModel.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.screen + +import android.arch.lifecycle.ViewModel +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import android.util.DisplayMetrics +import android.view.Display +import android.view.WindowManager +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import com.kgurgul.cpuinfo.utils.round2 +import java.util.* +import javax.inject.Inject + +/** + * ViewModel which is responsible for screen details + * + * @author kgurgul + */ +class ScreenInfoViewModel @Inject constructor( + private val resources: Resources, + private val windowManager: WindowManager) : ViewModel() { + + val dataObservableList = AdapterArrayList>() + + init { + getScreenData() + } + + /** + * Get all screen details + */ + private fun getScreenData() { + if (dataObservableList.isNotEmpty()) { + dataObservableList.clear() + } + dataObservableList.add(getScreenClass()) + dataObservableList.add(getDensityClass()) + dataObservableList.addAll(getInfoFromDisplayMetrics()) + } + + /** + * Classify screen into three main classes: large, normal, small + */ + private fun getScreenClass(): Pair { + val screenSize = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK + + val screenClass: String + + screenClass = when (screenSize) { + Configuration.SCREENLAYOUT_SIZE_LARGE -> resources.getString(R.string.large) + Configuration.SCREENLAYOUT_SIZE_NORMAL -> resources.getString(R.string.normal) + Configuration.SCREENLAYOUT_SIZE_SMALL -> resources.getString(R.string.small) + else -> resources.getString(R.string.unknown) + } + + return Pair(resources.getString(R.string.screen_class), screenClass) + } + + /** + * Classify screen into density classes: ldpi, mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi + */ + private fun getDensityClass(): Pair { + val densityDpi = resources.displayMetrics.densityDpi + + val densityClass: String + when (densityDpi) { + DisplayMetrics.DENSITY_LOW -> { + densityClass = "ldpi" + } + + DisplayMetrics.DENSITY_MEDIUM -> { + densityClass = "mdpi" + } + + DisplayMetrics.DENSITY_TV, DisplayMetrics.DENSITY_HIGH -> { + densityClass = "hdpi" + } + + DisplayMetrics.DENSITY_XHIGH, DisplayMetrics.DENSITY_280 -> { + densityClass = "xhdpi" + } + + DisplayMetrics.DENSITY_XXHIGH, DisplayMetrics.DENSITY_360, DisplayMetrics.DENSITY_400, + DisplayMetrics.DENSITY_420 -> { + densityClass = "xxhdpi" + } + + DisplayMetrics.DENSITY_XXXHIGH, DisplayMetrics.DENSITY_560 -> { + densityClass = "xxxhdpi" + } + + else -> { + densityClass = resources.getString(R.string.unknown) + } + } + + return Pair(resources.getString(R.string.density_class), densityClass) + } + + private fun getInfoFromDisplayMetrics(): ArrayList> { + val functionsList = ArrayList>() + + val display = windowManager.defaultDisplay + + val metrics = DisplayMetrics() + try { + if (Build.VERSION.SDK_INT >= 17) { + display.getRealMetrics(metrics) + functionsList.add(Pair(resources.getString(R.string.width), "${metrics.widthPixels}px")) + functionsList.add(Pair(resources.getString(R.string.height), "${metrics.heightPixels}px")) + } else { + val getRawH = Display::class.java.getMethod("getRawHeight") + val getRawW = Display::class.java.getMethod("getRawWidth") + + val height = getRawH.invoke(display) as Int + val width = getRawW.invoke(display) as Int + functionsList.add(Pair(resources.getString(R.string.width), "${width}px")) + functionsList.add(Pair(resources.getString(R.string.height), "${height}px")) + } + + val density = metrics.density + val dpHeight = metrics.heightPixels / density + val dpWidth = metrics.widthPixels / density + + functionsList.add(Pair(resources.getString(R.string.dp_width), "${dpWidth.toInt()}dp")) + functionsList.add(Pair(resources.getString(R.string.dp_height), "${dpHeight.toInt()}dp")) + functionsList.add(Pair(resources.getString(R.string.density), "$density")) + } catch (e: Exception) { + e.printStackTrace() + } + + display.getMetrics(metrics) + functionsList.add(Pair(resources.getString(R.string.absolute_width), "${metrics.widthPixels}px")) + functionsList.add(Pair(resources.getString(R.string.absolute_height), "${metrics.heightPixels}px")) + + val refreshRate = display.refreshRate + functionsList.add(Pair(resources.getString(R.string.refresh_rate), "${refreshRate.round2()}")) + + val orientation = display.rotation + functionsList.add(Pair(resources.getString(R.string.orientation), "$orientation")) + + return functionsList + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoFragment.kt new file mode 100644 index 00000000..7eee42d5 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoFragment.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.sensors + +import android.arch.lifecycle.ViewModelProviders +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter +import javax.inject.Inject + +/** + * Displays all data from device sensors + * + * @author kgurgul + */ +class SensorsInfoFragment : BaseRvFragment() { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private val viewModel: SensorsInfoViewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(SensorsInfoViewModel::class.java) + } + + override fun onStart() { + super.onStart() + viewModel.startProvidingData() + } + + override fun onStop() { + viewModel.stopProvidingData() + super.onStop() + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val infoItemsAdapter = InfoItemsAdapter(context, viewModel.dataObservableList, + InfoItemsAdapter.LayoutType.VERTICAL_LAYOUT) + recyclerView.adapter = infoItemsAdapter + lifecycle.addObserver(infoItemsAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoViewModel.kt new file mode 100644 index 00000000..f6295fa3 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/sensors/SensorsInfoViewModel.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.sensors + +import android.arch.lifecycle.ViewModel +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.Build +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import com.kgurgul.cpuinfo.utils.round1 +import java.util.* +import javax.inject.Inject + +/** + * ViewModel for reading sensors data + * + * @author kgurgul + */ +class SensorsInfoViewModel @Inject constructor( + private val sensorManager: SensorManager) : ViewModel(), SensorEventListener { + + val dataObservableList = AdapterArrayList>() + + private val sensorList: List = sensorManager.getSensorList(Sensor.TYPE_ALL) + + @Synchronized + fun startProvidingData() { + if (dataObservableList.isEmpty()) { + val functionsList = sensorList.mapTo(ArrayList>()) { + Pair(it.name, " ") + } + dataObservableList.addAll(functionsList) + } + + // Start register process on new Thread to avoid UI block + Thread { + for (sensor in sensorList) { + sensorManager.registerListener(SensorsViewModel@ this, sensor, + SensorManager.SENSOR_DELAY_NORMAL) + } + }.start() + } + + fun stopProvidingData() { + Thread { + sensorManager.unregisterListener(SensorsFragment@ this) + }.start() + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { + // Do nothing + } + + override fun onSensorChanged(event: SensorEvent) { + updateSensorInfo(event) + } + + /** + * Replace sensor value with the new one + */ + @Synchronized + private fun updateSensorInfo(event: SensorEvent) { + val updatedRowId = sensorList.indexOf(event.sensor) + dataObservableList[updatedRowId] = Pair(event.sensor.name, getSensorData(event)) + } + + /** + * Detect sensor type for passed [SensorEvent] and format it to the correct unit + */ + @Suppress("DEPRECATION") + private fun getSensorData(event: SensorEvent): String { + var data = " " + + val sensorType = event.sensor.type + when (sensorType) { + Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GRAVITY, Sensor.TYPE_LINEAR_ACCELERATION -> + data = "X=${event.values[0].round1()}m/s² Y=${ + event.values[1].round1()}m/s² Z=${event.values[2].round1()}m/s²" + Sensor.TYPE_GYROSCOPE -> + data = "X=${event.values[0].round1()}rad/s Y=${ + event.values[1].round1()}rad/s Z=${event.values[2].round1()}rad/s" + Sensor.TYPE_ROTATION_VECTOR -> + data = "X=${event.values[0].round1()} Y=${ + event.values[1].round1()} Z=${event.values[2].round1()}" + Sensor.TYPE_MAGNETIC_FIELD -> + data = "X=${event.values[0].round1()}μT Y=${ + event.values[1].round1()}μT Z=${event.values[2].round1()}μT" + Sensor.TYPE_ORIENTATION -> + data = "Azimuth=${event.values[0].round1()}° Pitch=${ + event.values[1].round1()}° Roll=${event.values[2].round1()}°" + Sensor.TYPE_PROXIMITY -> + data = "Distance=${event.values[0].round1()}cm" + Sensor.TYPE_AMBIENT_TEMPERATURE -> + data = "Air temperature=${event.values[0].round1()}°C" + Sensor.TYPE_LIGHT -> + data = "Illuminance=${event.values[0].round1()}lx" + Sensor.TYPE_PRESSURE -> + data = "Air pressure=${event.values[0].round1()}hPa" + Sensor.TYPE_RELATIVE_HUMIDITY -> + data = "Relative humidity=${event.values[0].round1()}%" + Sensor.TYPE_TEMPERATURE -> + data = "Device temperature=${event.values[0].round1()}°C" + } + + // TODO: Multiline support for this kind of data is necessary + if (Build.VERSION.SDK_INT >= 18) { + when (sensorType) { + Sensor.TYPE_GYROSCOPE_UNCALIBRATED -> + data = "X=${event.values[0].round1()}rad/s Y=${ + event.values[1].round1()}rad/s Z=${ + event.values[2].round1()}rad/s" /*\nEstimated drift: X=${ + event.values[3].round1() }rad/s Y=${ + event.values[4].round1() }rad/s Z=${ + event.values[5].round1() }rad/s"*/ + Sensor.TYPE_GAME_ROTATION_VECTOR -> + data = "X=${event.values[0].round1()} Y=${ + event.values[1].round1()} Z=${event.values[2].round1()}" + Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED -> + data = "X=${event.values[0].round1()}μT Y=${ + event.values[1].round1()}μT Z=${ + event.values[2].round1()}μT" /*\nIron bias: X=${ + event.values[3].round1() }μT Y=${ + event.values[4].round1() }μT Z=${ + event.values[5].round1() }μT"*/ + } + } + + if (Build.VERSION.SDK_INT >= 19) { + when (sensorType) { + Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR -> + data = "X=${event.values[0].round1()} Y=${ + event.values[1].round1()} Z=${event.values[2].round1()}" + } + } + + return data + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageAdapter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageAdapter.kt new file mode 100644 index 00000000..e3ab9135 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageAdapter.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.storage + +import android.arch.lifecycle.Lifecycle +import android.arch.lifecycle.LifecycleObserver +import android.arch.lifecycle.OnLifecycleEvent +import android.databinding.ObservableList +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.SimpleListChangeNotifier +import com.kgurgul.cpuinfo.utils.Utils +import com.kgurgul.cpuinfo.utils.round2 +import com.kgurgul.cpuinfo.widgets.progress.IconRoundCornerProgressBar +import java.lang.ref.WeakReference + +/** + * Adpter for items in [StorageInfoFragment]. It is copy from + * [com.kgurgul.cpuinfo.features.information.base.InfoItemsAdapter] because storage has + * another data container. It can be replaced with something more generic. + * + * @author kgurgul + */ +class StorageAdapter(private val storageObservableList: ObservableList) : + RecyclerView.Adapter(), LifecycleObserver { + + private val simpleListChangeNotifier = + SimpleListChangeNotifier(WeakReference(this)) + + init { + storageObservableList.addOnListChangedCallback(simpleListChangeNotifier) + } + + override fun getItemCount(): Int { + return storageObservableList.size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val v = LayoutInflater.from(parent.context).inflate(R.layout.item_storage, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bindViewHolder(storageObservableList[position]) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + storageObservableList.removeOnListChangedCallback(simpleListChangeNotifier) + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val storageDescriptionTv: TextView = itemView.findViewById(R.id.storage_description_tv) + private val storageProgress: IconRoundCornerProgressBar = itemView.findViewById(R.id.storage_progress) + + fun bindViewHolder(storageItem: StorageItem) { + val totalReadable = Utils.humanReadableByteCount(storageItem.storageTotal) + val usedReadable = Utils.humanReadableByteCount(storageItem.storageUsed) + val usedPercent = (storageItem.storageUsed.toFloat() + / storageItem.storageTotal.toFloat() * 100.0).round2() + + val storageDesc = "${storageItem.type}: $usedReadable / $totalReadable ($usedPercent%)" + storageDescriptionTv.text = storageDesc + + storageProgress.iconImageResource = storageItem.iconRes + storageProgress.progress = usedPercent.toFloat() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoFragment.kt new file mode 100644 index 00000000..32395666 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoFragment.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.storage + +import android.arch.lifecycle.ViewModelProviders +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Bundle +import android.os.Handler +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.di.Injectable +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.information.base.BaseRvFragment +import javax.inject.Inject + +/** + * Displays info about used and free storage on the device + * + * @author kgurgul + */ +class StorageInfoFragment : BaseRvFragment(), Injectable { + + private var receiverRegistered = false + + private val handler = Handler() + private val mountedReceiver = object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + handler.removeCallbacksAndMessages(null) + handler.postDelayed({ viewModel.refreshSdCard() }, 2000) + } + } + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private lateinit var viewModel: StorageInfoViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(this, viewModelInjectionFactory) + .get(StorageInfoViewModel::class.java) + } + + override fun onResume() { + super.onResume() + + // Register events connected with inserting SD card + if (!receiverRegistered) { + receiverRegistered = true + val filter = IntentFilter() + filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL) + filter.addAction(Intent.ACTION_MEDIA_CHECKING) + filter.addAction(Intent.ACTION_MEDIA_EJECT) + filter.addAction(Intent.ACTION_MEDIA_MOUNTED) + filter.addAction(Intent.ACTION_MEDIA_NOFS) + filter.addAction(Intent.ACTION_MEDIA_REMOVED) + filter.addAction(Intent.ACTION_MEDIA_SHARED) + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE) + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED) + filter.addDataScheme("file") + activity.registerReceiver(mountedReceiver, filter) + } + } + + override fun onPause() { + if (receiverRegistered) { + receiverRegistered = false + activity.unregisterReceiver(mountedReceiver) + handler.removeCallbacksAndMessages(null) + } + + super.onPause() + } + + override fun setupRecyclerViewAdapter() { + recyclerView.addItemDecoration(DividerItemDecoration(context)) + + val storageAdapter = StorageAdapter(viewModel.storageItemList) + recyclerView.adapter = storageAdapter + lifecycle.addObserver(storageAdapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoViewModel.kt new file mode 100644 index 00000000..9ae95914 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageInfoViewModel.kt @@ -0,0 +1,243 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.storage + +import android.arch.lifecycle.ViewModel +import android.content.res.Resources +import android.os.Environment +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import io.reactivex.Single +import io.reactivex.SingleEmitter +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.experimental.android.UI +import kotlinx.coroutines.experimental.async +import org.jetbrains.anko.coroutines.experimental.asReference +import org.jetbrains.anko.coroutines.experimental.bg +import timber.log.Timber +import java.io.* +import java.util.* +import javax.inject.Inject + +/** + * ViewModel for [StorageInfoFragment] + * + * @author kgurgul + */ +class StorageInfoViewModel @Inject constructor(private val resources: Resources) : ViewModel() { + + enum class MemoryType { INTERNAL, EXTERNAL } + + val storageItemList = AdapterArrayList() + + private val ref = asReference() + private var sdCardFinderDisposable: Disposable? = null + + init { + getStorageInfo() + } + + /** + * Get all available details about internal, external and secondary (SD card) storage + */ + private fun getStorageInfo() { + async(UI) { + val result = bg { getExternalAndInternalMemoryPair() } + val memoryPair = result.await() + ref().storageItemList.add(memoryPair.first) + if (memoryPair.second != null) { + ref().storageItemList.add(memoryPair.second as StorageItem) + } + refreshSdCard() + } + } + + /** + * @return pair of [StorageItem] where on the first place is internal storage and on the second + * external + */ + private fun getExternalAndInternalMemoryPair(): Pair { + val internalTotal = getTotalMemorySize(MemoryType.INTERNAL) + val internalUsed = internalTotal - getAvailableMemorySize(MemoryType.INTERNAL) + val internalMemory = StorageItem(resources.getString(R.string.internal), R.drawable.root, + internalTotal, internalUsed) + + var externalMemory: StorageItem? = null + if (isExternalMemoryAvailable()) { + val externalTotal = getTotalMemorySize(MemoryType.EXTERNAL) + val externalUsed = externalTotal - getAvailableMemorySize(MemoryType.EXTERNAL) + externalMemory = StorageItem(resources.getString(R.string.external), + R.drawable.folder, externalTotal, externalUsed) + } + return Pair(internalMemory, externalMemory) + } + + /** + * Try to get new data about SD card or remove it from the list + */ + @Synchronized + fun refreshSdCard() { + if (sdCardFinderDisposable == null || sdCardFinderDisposable!!.isDisposed) { + sdCardFinderDisposable = getSDCardFinder() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ sdCardFile -> + if (sdCardFile.totalSpace > 0) { + val sdTotal = sdCardFile.totalSpace + val sdUsed = sdTotal - sdCardFile.usableSpace + val sdMemory = StorageItem(resources.getString(R.string.external), + R.drawable.sdcard, sdTotal, sdUsed) + storageItemList.add(sdMemory) + } + }, { + Timber.i("Cannot find SD card file") + val storageItem = storageItemList.find { + it.iconRes == R.drawable.sdcard + } + if (storageItem != null) { + storageItemList.remove(storageItem) + } + }) + } + } + + /** + * @return true if device support external storage, otherwise false + */ + private fun isExternalMemoryAvailable(): Boolean { + return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED + } + + /** + * Get total bytes from internal or external storage. + * + * @param memoryType type of memory + * @return full size of the storage + */ + private fun getTotalMemorySize(memoryType: MemoryType): Long { + val path: File = when (memoryType) { + MemoryType.INTERNAL -> Environment.getDataDirectory() + MemoryType.EXTERNAL -> Environment.getExternalStorageDirectory() + } + + return path.totalSpace + } + + /** + * Get available bytes from internal or external storage. + * + * @param memoryType type of memory + * @return available size of the storage + */ + private fun getAvailableMemorySize(memoryType: MemoryType): Long { + val path: File = when (memoryType) { + MemoryType.INTERNAL -> Environment.getDataDirectory() + MemoryType.EXTERNAL -> Environment.getExternalStorageDirectory() + } + + return path.usableSpace + } + + /** + * @return [Single] with the SD card file or onError in case of missing that one. + */ + private fun getSDCardFinder(): Single { + return Single.create({ emitter: SingleEmitter -> + val mountedList = getExternalSDMounts() + + var strSDCardPath: String? = null + if (mountedList.size > 0) { + strSDCardPath = mountedList[0] + } + + if (strSDCardPath != null && strSDCardPath.isNotEmpty()) { + if (strSDCardPath.contains(":")) { + strSDCardPath = strSDCardPath.substring(0, strSDCardPath.indexOf(":")) + } + + val externalFilePath = File(strSDCardPath) + + if (externalFilePath.exists()) { + emitter.onSuccess(externalFilePath) + } else { + emitter.onError(FileNotFoundException("Cannot find SD card file")) + } + } else { + emitter.onError(FileNotFoundException("Cannot find SD card file")) + } + }) + } + + /** + * And there the magic starts :) TBH I'm not so sure that this is the only good solution but + * from my testing it is the working one for most of the phones. + */ + private fun getExternalSDMounts(): ArrayList { + val sdDirList = ArrayList() + try { + val dis = DataInputStream(FileInputStream("/proc/mounts")) + val br = BufferedReader(InputStreamReader(dis)) + val externalDir = Environment.getExternalStorageDirectory().path + while (true) { + val strLine = br.readLine() + if (strLine == null) { + break + } else if (!(strLine.contains("asec") + || strLine.contains("legacy") + || strLine.contains("Android/obb"))) { + if (strLine.startsWith("/dev/block/vold/") + || strLine.startsWith("/dev/block/sd") + || strLine.startsWith("/dev/fuse") + || strLine.startsWith("/mnt/media_rw")) { + val lineElements = + strLine.split(" ".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + val path = File(lineElements[1]) + if ((path.exists() + || path.isDirectory + || path.canWrite()) + && path.exists() + //&& path.canRead() + && !path.path.contains("/system") + && !sdDirList.contains(lineElements[1]) + && lineElements[1] != externalDir + && lineElements[1] != "/storage/emulated" + && !sdDirList.any { + it.endsWith(lineElements[1] + .substring(lineElements[1].lastIndexOf("/"), + lineElements[1].length)) + }) { + sdDirList.add(lineElements[1]) + } + } + } + } + dis.close() + } catch (e: Exception) { + Timber.i(e) + } + + return sdDirList + } + + override fun onCleared() { + super.onCleared() + sdCardFinderDisposable?.dispose() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageItem.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageItem.kt new file mode 100644 index 00000000..4c12ab5f --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/information/storage/StorageItem.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.information.storage + +/** + * Container for storage items (e.g. Internal, External, SD) + * + * @author kgurgul + */ +data class StorageItem(val type: String, + val iconRes: Int, + val storageTotal: Long, + val storageUsed: Long) \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessItem.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessItem.kt new file mode 100644 index 00000000..988ef720 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessItem.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.processes + +/** + * Domain model for processes + * + * @author kgurgul + */ +data class ProcessItem(val name: String, val pid: String, val ppid: String, val niceness: String, + val user: String, val rss: String, val vsize: String) \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesAdapter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesAdapter.kt new file mode 100644 index 00000000..4127706d --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesAdapter.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.processes + +import android.annotation.SuppressLint +import android.arch.lifecycle.Lifecycle +import android.arch.lifecycle.LifecycleObserver +import android.arch.lifecycle.OnLifecycleEvent +import android.databinding.ObservableList +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.SimpleListChangeNotifier +import com.kgurgul.cpuinfo.utils.Utils +import java.lang.ref.WeakReference + +/** + * Simple adapter for processes + * + * @author kgurgul + */ +class ProcessesAdapter(private val processList: ObservableList) + : RecyclerView.Adapter(), LifecycleObserver { + + private val simpleListChangeNotifier = + SimpleListChangeNotifier(WeakReference(this)) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder? { + val v = LayoutInflater.from(parent.context).inflate(R.layout.item_process, parent, false) + return ViewHolder(v) + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val process = processList[position] + holder.nameTv.text = process.name + holder.pidTv.text = "PID: ${process.pid}" + holder.ppidTv.text = "PPID: ${process.ppid}" + holder.nicenessTv.text = "NICENESS: ${process.niceness}" + holder.userTv.text = "USER: ${process.user}" + holder.rssTv.text = "RSS: ${Utils.humanReadableByteCount(process.rss.toLong() * 1024)}" + holder.vsizeTv.text = "VSZ: ${Utils.humanReadableByteCount(process.vsize.toLong() * 1024)}" + } + + override fun getItemCount(): Int { + return processList.size + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + processList.addOnListChangedCallback(simpleListChangeNotifier) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + processList.removeOnListChangedCallback(simpleListChangeNotifier) + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val nameTv: TextView = itemView.findViewById(R.id.proc_name_tv) + val pidTv: TextView = itemView.findViewById(R.id.proc_pid_tv) + val ppidTv: TextView = itemView.findViewById(R.id.proc_ppid_tv) + val nicenessTv: TextView = itemView.findViewById(R.id.proc_nic_tv) + val userTv: TextView = itemView.findViewById(R.id.proc_user_tv) + val rssTv: TextView = itemView.findViewById(R.id.proc_rss_tv) + val vsizeTv: TextView = itemView.findViewById(R.id.proc_vsize_tv) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesFragment.kt new file mode 100644 index 00000000..ef326c17 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesFragment.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.processes + +import android.arch.lifecycle.ViewModelProviders +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.SimpleItemAnimator +import android.view.* +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.list.DividerItemDecoration +import com.kgurgul.cpuinfo.databinding.FragmentProcessesBinding +import com.kgurgul.cpuinfo.di.Injectable +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.utils.AutoClearedValue +import javax.inject.Inject + +/** + * Displays list of running processes (works only to the API 24) + * + * @author kgurgul + */ +class ProcessesFragment : Fragment(), Injectable { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + private lateinit var viewModel: ProcessesViewModel + private lateinit var binding: AutoClearedValue + private lateinit var processesAdapter: AutoClearedValue + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(this, viewModelInjectionFactory) + .get(ProcessesViewModel::class.java) + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + binding = AutoClearedValue(this, + DataBindingUtil.inflate(inflater, R.layout.fragment_processes, container, false)) + binding.get().viewModel = viewModel + setupRecyclerView() + return binding.get().root + } + + /** + * Setup for [RecyclerView] + */ + private fun setupRecyclerView() { + processesAdapter = AutoClearedValue(this, ProcessesAdapter(viewModel.processList)) + + val rvLayoutManager = LinearLayoutManager(context) + binding.get().recyclerView.layoutManager = rvLayoutManager + + binding.get().recyclerView.adapter = processesAdapter.get() + binding.get().recyclerView.addItemDecoration(DividerItemDecoration(context)) + (binding.get().recyclerView.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false + lifecycle.addObserver(processesAdapter.get()) + } + + override fun onStart() { + super.onStart() + viewModel.startProcessRefreshing() + } + + override fun onStop() { + viewModel.stopProcessRefreshing() + super.onStop() + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.process_menu, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_sorting -> { + viewModel.changeProcessSorting() + true + } + else -> super.onOptionsItemSelected(item) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesViewModel.kt new file mode 100644 index 00000000..75416fa1 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/processes/ProcessesViewModel.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.processes + +import android.arch.lifecycle.ViewModel +import com.kgurgul.cpuinfo.common.Prefs +import com.kgurgul.cpuinfo.common.list.AdapterArrayList +import io.reactivex.Flowable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.experimental.android.UI +import kotlinx.coroutines.experimental.async +import org.jetbrains.anko.coroutines.experimental.asReference +import org.jetbrains.anko.coroutines.experimental.bg +import timber.log.Timber +import java.io.IOException +import java.util.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * ViewModel for [ProcessesFragment] + * + * @author kgurgul + */ +class ProcessesViewModel @Inject constructor(private val prefs: Prefs) : ViewModel() { + + private val SORTING_PROCESSES_KEY = "SORTING_PROCESSES_KEY" + + val processList = AdapterArrayList() + + private val ref = asReference() + private var isSortingAsc = prefs.get(SORTING_PROCESSES_KEY, true) + private var refreshingDisposable: Disposable? = null + + /** + * Start refreshing processes (every 5s) + */ + @Synchronized + fun startProcessRefreshing() { + if (refreshingDisposable == null || refreshingDisposable?.isDisposed == true) { + refreshingDisposable = Flowable.interval(0, 5, TimeUnit.SECONDS) + .onBackpressureDrop() + .flatMapSingle { getProcessListSingle() } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ newProcessList -> + Timber.i("Processes refreshed") + processList.replace(newProcessList) + }, { throwable -> + Timber.e(throwable) + }) + } + } + + /** + * Stop processes refreshing. + */ + fun stopProcessRefreshing() { + refreshingDisposable?.dispose() + } + + /** + * Change process list sorting type from ascending to descending or or vice versa + */ + fun changeProcessSorting() { + async(UI) { + val result = bg { getAppSortedList() } + val sortedAppList = result.await() + ref().processList.replace(sortedAppList) + } + } + + /** + * @return sorted list of the apps from [processList] + */ + private fun getAppSortedList(): List { + val processListCopy = ArrayList(processList) + isSortingAsc = !isSortingAsc + prefs.insert(SORTING_PROCESSES_KEY, isSortingAsc) + if (isSortingAsc) { + processListCopy.sortBy { it.name.toUpperCase() } + } else { + processListCopy.sortByDescending { it.name.toUpperCase() } + } + return processListCopy + } + + /** + * Return [Single] with process list + */ + private fun getProcessListSingle(): Single> { + return Single.fromCallable({ + val processesList = ArrayList() + processesList.addAll(readPs()) + if (isSortingAsc) { + processesList.sortBy { it.name.toUpperCase() } + } else { + processesList.sortByDescending { it.name.toUpperCase() } + } + processesList + }) + } + + /** + * Old method for reading ps output. Should be rewritten. + */ + private fun readPs(): List { + val processesList = ArrayList() + + try { + val args = arrayListOf("/system/bin/ps", "-p") + val cmd = ProcessBuilder(args) + + val process = cmd.start() + val bis = process.inputStream.bufferedReader() + + val lines = bis.readLines() + lines.forEachIndexed { i, line -> + if (i > 0) { + val st = StringTokenizer(line) + + // It is bad. Refactor this! + var iterator = 0 + var user = "" + var name = "" + var pid = "" + var ppid = "" + var niceness = "" + var rss = "" + var vsize = "" + + while (st.hasMoreTokens()) { + when (iterator) { + 0 -> user = st.nextToken() + 1 -> pid = st.nextToken() + 2 -> ppid = st.nextToken() + 3 -> vsize = st.nextToken() + 4 -> rss = st.nextToken() + 6 -> niceness = st.nextToken() + 12 -> name = st.nextToken() + else -> st.nextToken() + } + iterator++ + } + + processesList.add(ProcessItem(name, pid, ppid, niceness, user, rss, vsize)) + } + } + + bis.close() + } catch (e: IOException) { + Timber.e(e) + } + + return processesList + } + + override fun onCleared() { + super.onCleared() + refreshingDisposable?.dispose() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/settings/SettingsFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/settings/SettingsFragment.kt new file mode 100644 index 00000000..4bf9b50c --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/settings/SettingsFragment.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.settings + +import android.content.SharedPreferences +import android.os.Bundle +import android.support.v7.preference.ListPreference +import android.support.v7.preference.PreferenceFragmentCompat +import com.kgurgul.cpuinfo.R + +/** + * Provides possibility to change RAM widget refreshing time and temperature units + * + * @author kgurgul + */ +class SettingsFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener { + + companion object { + val KEY_TEMPERATURE_UNIT = "temperature_unit" + val KEY_RAM_REFRESHING = "ram_refreshing" + } + + private lateinit var temperatureUnitPreference: ListPreference + private lateinit var ramRefreshingPreference: ListPreference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences) + + temperatureUnitPreference = preferenceScreen.findPreference(KEY_TEMPERATURE_UNIT) + as ListPreference + ramRefreshingPreference = preferenceScreen.findPreference(KEY_RAM_REFRESHING) + as ListPreference + } + + override fun onResume() { + super.onResume() + + temperatureUnitPreference.summary = temperatureUnitPreference.entry.toString() + ramRefreshingPreference.summary = ramRefreshingPreference.entry.toString() + + preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + + preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + when (key) { + KEY_TEMPERATURE_UNIT -> + temperatureUnitPreference.summary = temperatureUnitPreference.entry.toString() + KEY_RAM_REFRESHING -> + ramRefreshingPreference.summary = ramRefreshingPreference.entry.toString() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFormatter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFormatter.kt new file mode 100644 index 00000000..af6c2e53 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFormatter.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature + +import com.kgurgul.cpuinfo.common.Prefs +import com.kgurgul.cpuinfo.features.settings.SettingsFragment +import com.kgurgul.cpuinfo.utils.round2 + +/** + * Format temperature using user settings + * + * @author kgurgul + */ +class TemperatureFormatter constructor(val prefs: Prefs) { + + private val CELSIUS = 0 + private val FAHRENHEIT = 1 + + /** + * Format temperature for current settings + * + * @param temp formatting temperature which will be formatted (passed in Celsius unit) + */ + fun format(temp: Float): String { + val tempUnit = prefs.get(SettingsFragment.KEY_TEMPERATURE_UNIT, CELSIUS.toString()) + .toInt() + return if (tempUnit == FAHRENHEIT) { + val fahrenheit = temp * 9 / 5 + 32 + "${fahrenheit.round2()}\u00B0F" + } else { + val tempFormatted = "${temp.toInt()}\u00B0C" + tempFormatted + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFragment.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFragment.kt new file mode 100644 index 00000000..2cf28418 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureFragment.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.SimpleItemAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.databinding.FragmentTemperatureBinding +import com.kgurgul.cpuinfo.di.Injectable +import com.kgurgul.cpuinfo.di.ViewModelInjectionFactory +import com.kgurgul.cpuinfo.features.temperature.list.TemperatureAdapter +import com.kgurgul.cpuinfo.features.temperature.list.TemperatureItem +import com.kgurgul.cpuinfo.utils.AutoClearedValue +import timber.log.Timber +import javax.inject.Inject + +/** + * Displays information about available temperatures. Remove activity wrappers when Lifecycle + * components will be integrated with support library. + * + * @author kgurgul + */ +class TemperatureFragment : Fragment(), Injectable { + + @Inject + lateinit var viewModelInjectionFactory: ViewModelInjectionFactory + + @Inject + lateinit var temperatureAdapter: AutoClearedValue + + private val viewModel by lazy { + ViewModelProviders.of(this, viewModelInjectionFactory) + .get(TemperatureViewModel::class.java) + } + + private lateinit var binding: AutoClearedValue + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + binding = AutoClearedValue(this, + DataBindingUtil.inflate(inflater, R.layout.fragment_temperature, container, false)) + binding.get().viewModel = viewModel + return binding.value?.root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + setupRecycleView() + } + + override fun onStart() { + super.onStart() + viewModel.startTemperatureRefreshing() + } + + override fun onStop() { + viewModel.stopTemperatureRefreshing() + super.onStop() + } + + /** + * Set all necessary data for [android.support.v7.widget.RecyclerView] + */ + private fun setupRecycleView() { + val layoutManager = LinearLayoutManager(context) + binding.get().tempRv.layoutManager = layoutManager + binding.get().tempRv.adapter = temperatureAdapter.get() + + viewModel.temperatureItemsLiveData.observe(this, Observer> { tempList -> + Timber.i("temperatureItemsLiveData observer refreshed") + temperatureAdapter.get().setTempItems(tempList) + }) + + // Remove change animation + val animator = binding.get().tempRv.itemAnimator + if (animator is SimpleItemAnimator) { + animator.supportsChangeAnimations = false + } + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureModule.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureModule.kt new file mode 100644 index 00000000..6b272399 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureModule.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature + +import com.kgurgul.cpuinfo.di.FragmentScope +import com.kgurgul.cpuinfo.features.temperature.list.TemperatureAdapter +import com.kgurgul.cpuinfo.utils.AutoClearedValue +import dagger.Module +import dagger.Provides + +/** + * Provides instances for [TemperatureFragment] + * + * @author kgurgul + */ +@FragmentScope +@Module +class TemperatureModule { + + @Provides + fun provideAutoClearedTempAdapter(temperatureFragment: TemperatureFragment, + temperatureFormatter: TemperatureFormatter): + AutoClearedValue { + return AutoClearedValue(temperatureFragment, TemperatureAdapter(temperatureFormatter)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureProvider.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureProvider.kt new file mode 100644 index 00000000..f1b2a580 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureProvider.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature + +import android.app.Application +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager +import android.text.TextUtils +import com.kgurgul.cpuinfo.utils.Utils +import io.reactivex.Maybe +import io.reactivex.rxkotlin.toObservable +import java.io.File + +/** + * Custom provider which provides all temperatures + * + * @author kgurgul + */ +class TemperatureProvider(val app: Application) { + + /** + * @return battery temperature + */ + fun getBatteryTemperature(): Int { + val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val batteryStatus = app.registerReceiver(null, iFilter) + return batteryStatus.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) / 10 + } + + /** + * @return CPU temperature + */ + fun getCpuTemp(path: String): Float? { + val temp = Utils.readOneLine(File(path)) ?: 0.0 + + return if (isTemperatureValid(temp)) { + temp.toFloat() + } else { + (temp / 1000).toFloat() + } + } + + /** + * Scan device looking for CPU temperature in all well known locations + */ + fun getCpuTemperatureFinder(): Maybe { + return CPU_TEMP_FILE_PATHS.toObservable() + .map { path -> + val temp = Utils.readOneLine(File(path)) + var validPath = "" + var currentTemp = 0.0 + if (temp != null) { + // Verify if we are in normal temperature range + if (isTemperatureValid(temp)) { + validPath = path + currentTemp = temp + } else if (isTemperatureValid(temp / 1000)) { + validPath = path + currentTemp = temp / 1000 + } + } + CpuTemperatureResult(validPath, currentTemp.toInt()) + } + .filter { (filePath) -> !TextUtils.isEmpty(filePath) } + .firstElement() + } + + /** + * Check if passed temperature is in normal range: -30 - 250 Celsius + * + * @param temp current temperature + */ + private fun isTemperatureValid(temp: Double): Boolean { + return temp >= -30.0 && temp <= 250.0 + } + + /** + * Container for temperature value and path + */ + data class CpuTemperatureResult(val filePath: String = "", val temp: Int = 0) + + // Ugly but currently working solution to get temperature is to search well known locations + // If you know better solution please refactor this :) + private val CPU_TEMP_FILE_PATHS = listOf( + "/sys/devices/system/cpu/cpu0/cpufreq/cpu_temp", + "/sys/devices/system/cpu/cpu0/cpufreq/FakeShmoo_cpu_temp", + "/sys/class/thermal/thermal_zone0/temp", + "/sys/class/i2c-adapter/i2c-4/4-004c/temperature", + "/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/temperature", + "/sys/devices/platform/omap/omap_temp_sensor.0/temperature", + "/sys/devices/platform/tegra_tmon/temp1_input", + "/sys/kernel/debug/tegra_thermal/temp_tj", + "/sys/devices/platform/s5p-tmu/temperature", + "/sys/class/thermal/thermal_zone1/temp", + "/sys/class/hwmon/hwmon0/device/temp1_input", + "/sys/devices/virtual/thermal/thermal_zone1/temp", + "/sys/devices/virtual/thermal/thermal_zone0/temp", + "/sys/class/thermal/thermal_zone3/temp", + "/sys/class/thermal/thermal_zone4/temp", + "/sys/class/hwmon/hwmonX/temp1_input", + "/sys/devices/platform/s5p-tmu/curr_temp") +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureViewModel.kt new file mode 100644 index 00000000..8cede219 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/TemperatureViewModel.kt @@ -0,0 +1,180 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature + +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.ViewModel +import android.content.res.Resources +import android.databinding.ObservableBoolean +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.common.Prefs +import com.kgurgul.cpuinfo.features.temperature.list.TemperatureItem +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.rxkotlin.subscribeBy +import io.reactivex.schedulers.Schedulers +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * ViewModel for [TemperatureActivity] + * + * @author kgurgul + */ +class TemperatureViewModel @Inject constructor( + private val prefs: Prefs, + private val resources: Resources, + private val temperatureProvider: TemperatureProvider) + : ViewModel() { + private val CPU_TEMP_RESULT_KEY = "temp_result_key" + + // Binding fields + val isLoading = ObservableBoolean(false) + val isError = ObservableBoolean(false) + + val temperatureItemsLiveData = MutableLiveData>() + + private var temperatureDisposable: Disposable? = null + private var refreshingDisposable: Disposable? = null + private var cpuTemperatureResult: TemperatureProvider.CpuTemperatureResult? = null + private var isBatteryTemperatureAvailable = false + + /** + * Start temperature getting process. It also validates all temperatures availability. + */ + fun startTemperatureRefreshing() { + Timber.i("startTemperatureRefreshing()") + if (prefs.contains(CPU_TEMP_RESULT_KEY)) { + cpuTemperatureResult = prefs.get(CPU_TEMP_RESULT_KEY, + TemperatureProvider.CpuTemperatureResult()) + verifyTemperaturesAvailability() + } else { + temperatureDisposable = getCpuAvailabilityTest() + } + } + + /** + * Stop temperature getting process + */ + fun stopTemperatureRefreshing() { + Timber.i("stopTemperatureRefreshing()") + refreshingDisposable?.dispose() + } + + /** + * Try to find path with CPU temperature. If success try validate temperatures and schedule + * refreshing process. + */ + private fun getCpuAvailabilityTest(): Disposable { + return temperatureProvider.getCpuTemperatureFinder() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { + isLoading.set(true) + isError.set(false) + } + .doFinally { + isLoading.set(false) + verifyTemperaturesAvailability() + } + .subscribeBy( + onSuccess = { temperatureResult -> + prefs.insert(CPU_TEMP_RESULT_KEY, temperatureResult) + cpuTemperatureResult = temperatureResult + }, + onError = { throwable -> + Timber.e(throwable) + }, + onComplete = { + Timber.i("List scan complete") + } + ) + } + + /** + * Verify which temperatures are available and schedule refreshing. If we don't have any + * temperature info set isError flag to true + */ + private fun verifyTemperaturesAvailability() { + if (!isBatteryTemperatureAvailable) { + val batteryTemp = temperatureProvider.getBatteryTemperature() + if (batteryTemp != 0) { + isBatteryTemperatureAvailable = true + } + } + + if (isBatteryTemperatureAvailable || cpuTemperatureResult != null) { + scheduleRefreshing() + } else { + isError.set(true) + } + } + + /** + * Schedule refreshing process (for 3s) + */ + private fun scheduleRefreshing() { + if (refreshingDisposable != null) { + refreshingDisposable!!.dispose() + } + + refreshingDisposable = Observable.interval(0, 3, TimeUnit.SECONDS) + .map { + var batteryTemp: Int? = null + if (isBatteryTemperatureAvailable) { + batteryTemp = temperatureProvider.getBatteryTemperature() + } + var cpuTemp: Float? = null + if (cpuTemperatureResult != null) { + cpuTemp = temperatureProvider.getCpuTemp(cpuTemperatureResult!!.filePath) + } + TempContainer(cpuTemp, batteryTemp) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy( + { (cpuTemp, batteryTemp) -> + val temporaryTempList = ArrayList() + if (cpuTemp != null) { + temporaryTempList.add(TemperatureItem(R.drawable.ic_cpu_temp, + resources.getString(R.string.cpu), cpuTemp)) + } + if (batteryTemp != null) { + temporaryTempList.add(TemperatureItem(R.drawable.ic_battery, + resources.getString(R.string.battery), + batteryTemp.toFloat())) + } + + temperatureItemsLiveData.value = temporaryTempList + }, + { throwable -> + Timber.e(throwable) + } + ) + } + + override fun onCleared() { + super.onCleared() + Timber.i("onCleared()") + temperatureDisposable?.dispose() + refreshingDisposable?.dispose() + } + + private data class TempContainer(val cpuTemp: Float?, val batteryTemp: Int?) +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureAdapter.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureAdapter.kt new file mode 100644 index 00000000..5946a93f --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureAdapter.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature.list + +import android.databinding.DataBindingUtil +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.ViewGroup +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.databinding.ItemTemperatureBinding +import com.kgurgul.cpuinfo.features.temperature.TemperatureFormatter + +/** + * Temperature list adapter which observe temperatureItemsLiveData list + * + * @author kgurgul + */ +class TemperatureAdapter(private val temperatureFormatter: TemperatureFormatter) + : RecyclerView.Adapter() { + + var temperatureItems: List? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { + val viewHolderBinding = DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + R.layout.item_temperature, + parent, + false) + return BindingViewHolder(viewHolderBinding) + } + + override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { + val temperatureItem = temperatureItems!![position] + holder.bindTemperatureItem(temperatureItem, temperatureFormatter) + } + + override fun getItemCount(): Int { + return temperatureItems?.size ?: 0 + } + + fun setTempItems(tempItems: List?) { + temperatureItems = tempItems + notifyDataSetChanged() + } + + class BindingViewHolder(private val binding: ItemTemperatureBinding) + : RecyclerView.ViewHolder(binding.root) { + + fun bindTemperatureItem(temperatureItem: TemperatureItem, + temperatureFormatter: TemperatureFormatter) { + if (binding.viewModel == null) { + binding.viewModel = TemperatureItemViewModel(temperatureItem, temperatureFormatter) + } else { + binding.viewModel.setTempItem(temperatureItem) + } + } + } +} + diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItem.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItem.kt new file mode 100644 index 00000000..94624478 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItem.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature.list + +/** + * Model for temperature items + */ +data class TemperatureItem(val iconRes: Int, val name: String, var temperature: Float) \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItemViewModel.kt b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItemViewModel.kt new file mode 100644 index 00000000..1508718c --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/features/temperature/list/TemperatureItemViewModel.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.features.temperature.list + +import android.databinding.BaseObservable +import android.databinding.BindingAdapter +import android.widget.ImageView +import com.kgurgul.cpuinfo.features.temperature.TemperatureFormatter + +/** + * ViewModel for temperature item + * + * @author kgurgul + */ +class TemperatureItemViewModel( + private var temperatureItem: TemperatureItem, + private val temperatureFormatter: TemperatureFormatter) : BaseObservable() { + + companion object { + @JvmStatic + @BindingAdapter("srcCompat") + fun loadImage(imageView: ImageView, image: Int) { + imageView.setImageResource(image) + } + } + + fun setTempItem(temperatureItem: TemperatureItem) { + this.temperatureItem = temperatureItem + notifyChange() + } + + fun getUnitIcon(): Int { + return temperatureItem.iconRes + } + + fun getUnitName(): String { + return temperatureItem.name + } + + fun getTempValue(): String { + return temperatureFormatter.format(temperatureItem.temperature) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/AutoClearedValue.kt b/app/src/main/java/com/kgurgul/cpuinfo/utils/AutoClearedValue.kt new file mode 100644 index 00000000..d0d1bcd1 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/AutoClearedValue.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.utils + +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager + +/** + * Wrapper which automatically release resources + * + * @author kgurgul + */ +class AutoClearedValue(fragment: Fragment, var value: T?) { + + init { + val fragmentManager = fragment.fragmentManager + fragmentManager.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentViewDestroyed(fm: FragmentManager, f: Fragment) { + if (fragment == f) { + this@AutoClearedValue.value = null + fragmentManager.unregisterFragmentLifecycleCallbacks(this) + } + } + }, false) + } + + fun get(): T = + value!! +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/Extensions.kt b/app/src/main/java/com/kgurgul/cpuinfo/utils/Extensions.kt new file mode 100644 index 00000000..4076520b --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/Extensions.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.utils + +import android.os.Build + +/** + * All basic extensions + * + * @author kgurgul + */ +fun Float.round1(): Float { + return Math.round(this * 10.0) / 10.0f +} + +fun Double.round1(): Double { + return Math.round(this * 10.0) / 10.0 +} + +fun Float.round2(): Float { + return Math.round(this * 100.0) / 100.0f +} + +fun Double.round2(): Double { + return Math.round(this * 100.0) / 100.0 +} + +inline fun runOnApi(api: Int, f: () -> Unit, otherwise: () -> Unit) { + if (Build.VERSION.SDK_INT == api) { + f() + } else { + otherwise() + } +} + +inline fun runOnApiBelow(api: Int, f: () -> Unit, otherwise: () -> Unit) { + if (Build.VERSION.SDK_INT < api) { + f() + } else { + otherwise() + } +} + +inline fun runOnApiAbove(api: Int, f: () -> Unit, otherwise: () -> Unit) { + if (Build.VERSION.SDK_INT > api) { + f() + } else { + otherwise() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/SingleLiveEvent.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/SingleLiveEvent.java new file mode 100644 index 00000000..1a10207b --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/SingleLiveEvent.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.utils; + +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Observer; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + *

+ * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + *

+ * Note that only one observer is going to be notified of changes. + *

+ * Fork from: https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java + */ +public class SingleLiveEvent extends MutableLiveData { + + private static final String TAG = "SingleLiveEvent"; + + private final AtomicBoolean mPending = new AtomicBoolean(false); + + @MainThread + public void observe(LifecycleOwner owner, final Observer observer) { + + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); + } + + // Observe the internal MutableLiveData + super.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t); + } + } + }); + } + + @MainThread + public void setValue(@Nullable T t) { + mPending.set(true); + super.setValue(t); + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + public void call() { + setValue(null); + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/Utils.kt b/app/src/main/java/com/kgurgul/cpuinfo/utils/Utils.kt new file mode 100644 index 00000000..0047a965 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/Utils.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.utils + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import java.io.BufferedReader +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.* + +/** + * General utility class + * + * @author kgurgul + */ +object Utils { + + /** + * Helper which adds item to list if value is not null and not empty + */ + fun addPairIfExists(array: ArrayList>, key: String, value: String?) { + if (value != null && !value.isEmpty()) { + array.add(Pair(key, value)) + } + } + + /** + * Convert bytes into normalized unit string + */ + fun humanReadableByteCount(bytes: Long): String { + val unit = 1024 + if (bytes < unit) return "$bytes B" + val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt() + val pre = "KMGTPE"[exp - 1] + return String.format(Locale.US, "%.2f %sB", bytes / Math.pow(unit.toDouble(), + exp.toDouble()), pre) + } + + /** + * Format passed bytes into megabytes string + */ + fun convertBytesToMega(bytes: Long): String { + val megaBytes = bytes.toDouble() / (1024.0 * 1024.0) + val df = DecimalFormat("#.##", DecimalFormatSymbols(Locale.US)) + + return "${df.format(megaBytes)} MB" + } + + /** + * Open google with passed query + */ + fun searchInGoogle(context: Context, query: String) { + val uri = Uri.parse("http://www.google.com/#q=$query") + val intent = Intent(Intent.ACTION_VIEW, uri) + context.startActivity(intent) + } + + /** + * Read and parse to Long first line from file + */ + fun readOneLine(file: File): Double? { + val text: String? + try { + val fs = FileInputStream(file) + val sr = InputStreamReader(fs) + val br = BufferedReader(sr) + text = br.readLine() + br.close() + sr.close() + fs.close() + } catch (ex: Exception) { + return null + } + + val value: Double? + try { + value = text.toDouble() + } catch (nfe: NumberFormatException) { + return null + } + + return value + } + + /** + * Get device hardware info like: Manufacturer, Brand, Model, Serial + * + * @return ID concatenated hardware info + */ + fun getHardwareInfo(): String { + return Build.MANUFACTURER + " " + + Build.BRAND + " " + + Build.MODEL + " " + + Build.VERSION.SDK_INT + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/AndroidProcesses.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/AndroidProcesses.java new file mode 100755 index 00000000..c5e770be --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/AndroidProcesses.java @@ -0,0 +1,297 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.utils.processmanager; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.util.Log; + +import com.kgurgul.cpuinfo.utils.processmanager.models.AndroidAppProcess; +import com.kgurgul.cpuinfo.utils.processmanager.models.AndroidProcess; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + *

Helper class to get a list of processes on Android.

+ *
+ *

Usage:

+ *

+ *

Get a list of running apps:

+ *
+ * List<AndroidAppProcess> processes = AndroidProcesses.getRunningAppProcesses();
+ * 
+ *

+ *

Get some information about a process:

+ *
+ * AndroidAppProcess process = processes.get(location);
+ * String processName = process.name;
+ *
+ * Stat stat = process.stat();
+ * int pid = stat.getPid();
+ * int parentProcessId = stat.ppid();
+ * long startTime = stat.stime();
+ * int policy = stat.policy();
+ * char state = stat.state();
+ *
+ * Statm statm = process.statm();
+ * long totalSizeOfProcess = statm.getSize();
+ * long residentSetSize = statm.getResidentSetSize();
+ *
+ * PackageInfo packageInfo = process.getPackageInfo(context, 0);
+ * String appName = packageInfo.applicationInfo.loadLabel(pm).toString();
+ * 
+ *

+ *

Check if your app is in the foreground:

+ *
+ * if (AndroidProcesses.isMyProcessInTheForeground()) {
+ *   // do stuff
+ * }
+ * 
+ *

+ *

Get a list of application processes that are running on the device:

+ *
+ * List<ActivityManager.RunningAppProcessInfo> processes = AndroidProcesses.getRunningAppProcessInfo(context);
+ * 
+ *

+ *


+ *

Limitations

+ *

+ *

System apps may not be visible because they have a higher SELinux context than third party apps.

+ *

Some information that was available through {@link ActivityManager#getRunningAppProcesses()} is not available + * using this library + * ({@link RunningAppProcessInfo#pkgList}, + * {@link RunningAppProcessInfo#lru}, + * {@link RunningAppProcessInfo#importance}, + * etc.).

+ *

This is currently not working on the N developer preview.

+ *
+ *

Note: You should avoid running methods from this class on the UI thread.

+ */ +public class AndroidProcesses { + + public static final String TAG = "AndroidProcesses"; + + private static boolean loggingEnabled; + + /* package */ AndroidProcesses() { + throw new AssertionError("no instances"); + } + + /** + * @return {@code true} if logging is enabled. + * @see #setLoggingEnabled(boolean) + */ + public static boolean isLoggingEnabled() { + return loggingEnabled; + } + + /** + * Toggle whether debug logging is enabled. + * + * @param enabled {@code true} to enable logging. This should be only be used for debugging purposes. + * @see #isLoggingEnabled() + * @see #log(String, Object...) + * @see #log(Throwable, String, Object...) + */ + public static void setLoggingEnabled(boolean enabled) { + loggingEnabled = enabled; + } + + /** + * Send a log message if logging is enabled. + * + * @param message the message to log + * @param args list of arguments to pass to the formatter + */ + public static void log(String message, Object... args) { + if (loggingEnabled) { + Log.d(TAG, args.length == 0 ? message : String.format(message, args)); + } + } + + /** + * Send a log message if logging is enabled. + * + * @param error An exception to log + * @param message the message to log + * @param args list of arguments to pass to the formatter + */ + public static void log(Throwable error, String message, Object... args) { + if (loggingEnabled) { + Log.d(TAG, args.length == 0 ? message : String.format(message, args), error); + } + } + + /** + * @return a list of all processes running on the device. + */ + public static List getRunningProcesses() { + List processes = new ArrayList<>(); + File[] files = new File("/proc").listFiles(); + for (File file : files) { + if (file.isDirectory()) { + int pid; + try { + pid = Integer.parseInt(file.getName()); + } catch (NumberFormatException e) { + continue; + } + try { + processes.add(new AndroidProcess(pid)); + } catch (IOException e) { + log(e, "Error reading from /proc/%d.", pid); + // System apps will not be readable on Android 5.0+ if SELinux is enforcing. + // You will need root access or an elevated SELinux context to read all files under /proc. + } + } + } + return processes; + } + + /** + * @return a list of all running app processes on the device. + */ + public static List getRunningAppProcesses() { + List processes = new ArrayList<>(); + File[] files = new File("/proc").listFiles(); + for (File file : files) { + if (file.isDirectory()) { + int pid; + try { + pid = Integer.parseInt(file.getName()); + } catch (NumberFormatException e) { + continue; + } + try { + processes.add(new AndroidAppProcess(pid)); + } catch (AndroidAppProcess.NotAndroidAppProcessException ignored) { + } catch (IOException e) { + log(e, "Error reading from /proc/%d.", pid); + // System apps will not be readable on Android 5.0+ if SELinux is enforcing. + // You will need root access or an elevated SELinux context to read all files under /proc. + } + } + } + return processes; + } + + /** + * Get a list of user apps running in the foreground. + * + * @param context the application context + * @return a list of user apps that are in the foreground. + */ + public static List getRunningForegroundApps(Context context) { + List processes = new ArrayList<>(); + File[] files = new File("/proc").listFiles(); + PackageManager pm = context.getPackageManager(); + for (File file : files) { + if (file.isDirectory()) { + int pid; + try { + pid = Integer.parseInt(file.getName()); + } catch (NumberFormatException e) { + continue; + } + try { + AndroidAppProcess process = new AndroidAppProcess(pid); + if (process.foreground + // ignore system processes. First app user starts at 10000. + && (process.uid < 1000 || process.uid > 9999) + // ignore processes that are not running in the default app process. + && !process.name.contains(":") + // Ignore processes that the user cannot launch. + && pm.getLaunchIntentForPackage(process.getPackageName()) != null) { + processes.add(process); + } + } catch (AndroidAppProcess.NotAndroidAppProcessException ignored) { + } catch (IOException e) { + log(e, "Error reading from /proc/%d.", pid); + // System apps will not be readable on Android 5.0+ if SELinux is enforcing. + // You will need root access or an elevated SELinux context to read all files under /proc. + } + } + } + return processes; + } + + /** + * @return {@code true} if this process is in the foreground. + */ + public static boolean isMyProcessInTheForeground() { + try { + return new AndroidAppProcess(android.os.Process.myPid()).foreground; + } catch (Exception e) { + log(e, "Error finding our own process"); + } + return false; + } + + /** + * Returns a list of application processes that are running on the device. + *

+ *

NOTE: On Lollipop (SDK 22) this does not provide + * {@link RunningAppProcessInfo#pkgList}, + * {@link RunningAppProcessInfo#importance}, + * {@link RunningAppProcessInfo#lru}, + * {@link RunningAppProcessInfo#importanceReasonCode}, + * {@link RunningAppProcessInfo#importanceReasonComponent}, + * {@link RunningAppProcessInfo#importanceReasonPid}, + * etc. If you need more process information try using + * {@link #getRunningAppProcesses()} or {@link android.app.usage.UsageStatsManager}

+ * + * @param context the application context + * @return a list of RunningAppProcessInfo records, or null if there are no + * running processes (it will not return an empty list). This list ordering is not + * specified. + */ + public static List getRunningAppProcessInfo(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + List runningAppProcesses = AndroidProcesses.getRunningAppProcesses(); + List appProcessInfos = new ArrayList<>(); + for (AndroidAppProcess process : runningAppProcesses) { + RunningAppProcessInfo info = new RunningAppProcessInfo(process.name, process.pid, null); + info.uid = process.uid; + // TODO: Get more information about the process. pkgList, importance, lru, etc. + appProcessInfos.add(info); + } + return appProcessInfos; + } + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + return am.getRunningAppProcesses(); + } + + /** + * A {@link Comparator} to list processes by name + */ + public static final class ProcessComparator implements Comparator { + + @Override + public int compare(AndroidProcess p1, AndroidProcess p2) { + return p1.name.compareToIgnoreCase(p2.name); + } + + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidAppProcess.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidAppProcess.java new file mode 100755 index 00000000..1beb34f9 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidAppProcess.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Parcel; + +import java.io.File; +import java.io.IOException; + +public class AndroidAppProcess extends AndroidProcess { + + public static final Creator CREATOR = new Creator() { + + @Override + public AndroidAppProcess createFromParcel(Parcel source) { + return new AndroidAppProcess(source); + } + + @Override + public AndroidAppProcess[] newArray(int size) { + return new AndroidAppProcess[size]; + } + }; + private static final boolean SYS_SUPPORTS_SCHEDGROUPS = new File("/dev/cpuctl/tasks").exists(); + /** + * {@code true} if the process is in the foreground + */ + public final boolean foreground; + /** + * The user id of this process. + */ + public final int uid; + + public AndroidAppProcess(int pid) throws IOException, NotAndroidAppProcessException { + super(pid); + final boolean foreground; + int uid; + + if (SYS_SUPPORTS_SCHEDGROUPS) { + Cgroup cgroup = cgroup(); + ControlGroup cpuacct = cgroup.getGroup("cpuacct"); + ControlGroup cpu = cgroup.getGroup("cpu"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (cpu == null || cpuacct == null || !cpuacct.group.contains("pid_")) { + throw new NotAndroidAppProcessException(pid); + } + foreground = !cpu.group.contains("bg_non_interactive"); + try { + uid = Integer.parseInt(cpuacct.group.split("/")[1].replace("uid_", "")); + } catch (Exception e) { + uid = status().getUid(); + } + + } else { + if (cpu == null || cpuacct == null || !cpu.group.contains("apps")) { + throw new NotAndroidAppProcessException(pid); + } + foreground = !cpu.group.contains("bg_non_interactive"); + try { + uid = Integer.parseInt(cpuacct.group.substring(cpuacct.group.lastIndexOf("/") + 1)); + } catch (Exception e) { + uid = status().getUid(); + } + + } + } else { + // this is a really ugly way to check if the process is an application. + // we could possibly check the UID name (starts with "app_" or "u_a") + if (name.startsWith("/") || !new File("/data/data", getPackageName()).exists()) { + throw new NotAndroidAppProcessException(pid); + } + Stat stat = stat(); + Status status = status(); + // https://github.com/android/platform_system_core/blob/jb-mr1-release/libcutils/sched_policy.c#L245-256 + foreground = stat.policy() == 0; // SCHED_NORMAL + uid = status.getUid(); + } + + this.foreground = foreground; + this.uid = uid; + } + + protected AndroidAppProcess(Parcel in) { + super(in); + foreground = in.readByte() != 0x00; + uid = in.readInt(); + } + + /** + * @return the app's package name + * @see #name + */ + public String getPackageName() { + return name.split(":")[0]; + } + + /** + * Retrieve overall information about the application package. + *

+ *

Throws {@link PackageManager.NameNotFoundException} if a package with the given name can + * not be found on the system.

+ * + * @param context the application context + * @param flags Additional option flags. Use any combination of + * {@link PackageManager#GET_ACTIVITIES}, {@link PackageManager#GET_GIDS}, + * {@link PackageManager#GET_CONFIGURATIONS}, {@link PackageManager#GET_INSTRUMENTATION}, + * {@link PackageManager#GET_PERMISSIONS}, {@link PackageManager#GET_PROVIDERS}, + * {@link PackageManager#GET_RECEIVERS}, {@link PackageManager#GET_SERVICES}, + * {@link PackageManager#GET_SIGNATURES}, {@link PackageManager#GET_UNINSTALLED_PACKAGES} + * to modify the data returned. + * @return a PackageInfo object containing information about the package. + */ + public PackageInfo getPackageInfo(Context context, int flags) + throws PackageManager.NameNotFoundException { + return context.getPackageManager().getPackageInfo(getPackageName(), flags); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (foreground ? 0x01 : 0x00)); + dest.writeInt(uid); + } + + public static final class NotAndroidAppProcessException extends Exception { + + public NotAndroidAppProcessException(int pid) { + super(String.format("The process %d does not belong to any application", pid)); + } + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidProcess.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidProcess.java new file mode 100755 index 00000000..d32299a2 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/AndroidProcess.java @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.io.IOException; + +public class AndroidProcess implements Parcelable { + + public static final Creator CREATOR = new Creator() { + + @Override + public AndroidProcess createFromParcel(Parcel source) { + return new AndroidProcess(source); + } + + @Override + public AndroidProcess[] newArray(int size) { + return new AndroidProcess[size]; + } + }; + /** + * the process name + */ + public final String name; + + /** + * the process id + */ + public final int pid; + + /** + * AndroidProcess constructor + * + * @param pid the process id + * @throws IOException if /proc/[pid] does not exist or we don't have read access. + */ + public AndroidProcess(int pid) throws IOException { + this.pid = pid; + this.name = getProcessName(pid); + } + + protected AndroidProcess(Parcel in) { + this.name = in.readString(); + this.pid = in.readInt(); + } + + /** + * Get the name of a running process. + * + * @param pid the process id. + * @return the name of the process. + * @throws IOException if the file does not exist or we don't have read permissions. + */ + static String getProcessName(int pid) throws IOException { + String cmdline = null; + try { + cmdline = ProcFile.readFile(String.format("/proc/%d/cmdline", pid)).trim(); + } catch (IOException ignored) { + } + if (TextUtils.isEmpty(cmdline)) { + return Stat.get(pid).getComm(); + } + return cmdline; + } + + /** + * Read the contents of a file in /proc/[pid]/[filename]. + * + * @param filename the relative path to the file. + * @return the contents of the file. + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public String read(String filename) throws IOException { + return ProcFile.readFile(String.format("/proc/%d/%s", pid, filename)); + } + + /** + *

/proc/[pid]/attr/current (since Linux 2.6.0)

+ *

+ *

The contents of this file represent the current security attributes of the process.

+ *

+ *

In SELinux, this file is used to get the security context of a process. Prior to Linux + * 2.6.11, this file could not be used to set the security context (a write was always denied), + * since SELinux limited process security transitions to execve(2) (see the description of + * /proc/[pid]/attr/exec, below). ince Linux 2.6.11, SELinux lifted this restriction and began + * supporting "set" operations via writes to this node if authorized by policy, although use of + * this operation is only suitable for applications that are trusted to maintain any desired + * separation between the old and new security contexts. Prior to Linux 2.6.28, SELinux did not + * allow threads within a multi- threaded process to set their security context via this node as + * it would yield an inconsistency among the security contexts of the threads sharing the same + * memory space. Since Linux 2.6.28, SELinux lifted this restriction and began supporting "set" + * operations for threads within a multithreaded process if the new security context is bounded + * by the old security context, where the bounded relation is defined in policy and guarantees + * that the new security context has a subset of the permissions of the old security context. + * Other security modules may choose to support "set" operations via writes to this node.

+ * + * @return the contents of /proc/[pid]/attr/current + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public String attr_current() throws IOException { + return read("attr/current"); + } + + /** + *

/proc/[pid]/cmdline

+ *

+ *

This read-only file holds the complete command line for the process, unless the process is + * a zombie. In the latter case, there is nothing in this file: that is, a read on this file will + * return 0 characters. The command-line arguments appear in this file as a set of strings + * separated by null bytes ('\0'), with a further null byte after the last string.

+ * + * @return the name of the process. (note: process name may be empty. In case it is empty get + * the process name from /proc/[pid]/stat). + * @throws IOException if the file does not exist or we don't have read permissions. + * @see #name + */ + public String cmdline() throws IOException { + return read("cmdline"); + } + + /** + *

/proc/[pid]/cgroup (since Linux 2.6.24)

+ *

+ *

This file describes control groups to which the process/task belongs. For each cgroup + * hierarchy there is one entry containing colon-separated fields of the form:

+ *

+ *

5:cpuacct,cpu,cpuset:/daemons

+ *

+ *

The colon-separated fields are, from left to right:

+ *

+ *

    + *
  1. hierarchy ID number
  2. + *
  3. set of subsystems bound to the hierarchy
  4. + *
  5. control group in the hierarchy to which the process belongs
  6. + *
+ *

+ *

This file is present only if the CONFIG_CGROUPS kernel configuration option is enabled.

+ * + * @return the {@link Cgroup} for this process + * @throws IOException + */ + public Cgroup cgroup() throws IOException { + return Cgroup.get(pid); + } + + /** + *

/proc/[pid]/oom_score (since Linux 2.6.11)

+ *

+ *

This file displays the current score that the kernel gives to this process for the + * purpose of selecting a process for the OOM-killer. A higher score means that the + * process is more likely to be selected by the OOM-killer.

+ *

+ *

The basis for this score is the amount of memory used by the process, with + * increases (+) or decreases (-) for factors including:

+ *

+ *

    + *
  • whether the process creates a lot of children using fork(2)(+);
  • + *
  • whether the process has been running a long time, or has used a lot of CPU time (-);
  • + *
  • whether the process has a low nice value (i.e., > 0) (+);
  • + *
  • whether the process is privileged (-); and
  • + *
  • whether the process is making direct hardware access (-).
  • + *
+ *

+ *

The oom_score also reflects the adjustment specified by the oom_score_adj + * or oom_adj setting for the process.

+ * + * @return the oom_score value for this process + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public int oom_score() throws IOException { + return Integer.parseInt(read("oom_score")); + } + + /** + *

/proc/[pid]/oom_adj (since Linux 2.6.11)

+ *

+ *

This file can be used to adjust the score used to select which process should be killed in + * an out-of-memory (OOM) situation. The kernel uses this value for a bit-shift operation of the + * process's oom_score value: valid values are in the* range -16 to +15, plus the special value + * -17, which disables OOM-killing altogether for this process. A positive score increases the + * likelihood of this process being killed by the OOM-killer; a negative score decreases the + * likelihood.

+ *

+ *

The default value for this file is 0; a new process inherits its parent's oom_adj setting. + * A process must be privileged (CAP_SYS_RESOURCE) to update this file.

+ *

+ *

Since Linux 2.6.36, use of this file is deprecated in favor of + * /proc/[pid]/oom_score_adj.

+ * + * @return the oom_adj value for this process + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public int oom_adj() throws IOException { + return Integer.parseInt(read("oom_adj")); + } + + /** + *

/proc/[pid]/oom_score_adj (since Linux 2.6.36)

+ *

+ *

This file can be used to adjust the badness heuristic used to select which process gets + * killed in out-of-memory conditions.

+ *

+ *

The badness heuristic assigns a value to each candidate task ranging from 0 (never kill) to + * 1000 (always kill) to determine which process is targeted. The units are roughly a proportion + * along that range of allowed memory the process may allocate from, based on an estimation of + * its current memory and swap use. For example, if a task is using all allowed memory, its + * badness score will be 1000. If it is using half of its allowed memory, its score will be + * 500.

+ *

+ *

There is an additional factor included in the badness score: root processes are given 3% + * extra memory over other tasks.

+ *

+ *

The amount of "allowed" memory depends on the context in which the OOM-killer was called. + * If it is due to the memory assigned to the allocating task's cpuset being exhausted, the + * allowed memory represents the set of mems assigned to that cpuset (see cpuset(7)). If it is + * due to a mempolicy's node(s) being exhausted, the allowed memory represents the set of + * mempolicy nodes. If it is due to a memory limit (or swap limit) being reached, the allowed + * memory is that configured limit. Finally, if it is due to the entire system being out of + * memory, the allowed memory represents all allocatable resources.

+ *

+ *

The value of oom_score_adj is added to the badness score before it is used to determine + * which task to kill. Acceptable values range from -1000 (OOM_SCORE_ADJ_MIN) to +1000 + * (OOM_SCORE_ADJ_MAX). This allows user space to control the preference for OOM-killing, ranging + * from always preferring a certain task or completely disabling it from OOM killing. The lowest + * possible value, -1000, is equivalent to disabling OOM- killing entirely for that task, since + * it will always report a badness score of 0.

+ *

+ *

Consequently, it is very simple for user space to define the amount of memory to consider + * for each task. Setting a oom_score_adj value of +500, for example, is roughly equivalent to + * allowing the remainder of tasks sharing the same system, cpuset, mempolicy, or memory + * controller resources to use at least 50% more memory. A value of -500, on the other hand, + * would be roughly equivalent to discounting 50% of the task's allowed memory from being + * considered as scoring against the task.

+ *

+ *

For backward compatibility with previous kernels, /proc/[pid]/oom_adj can still be used to + * tune the badness score. Its value is scaled linearly with oom_score_adj.

+ *

+ *

Writing to /proc/[pid]/oom_score_adj or /proc/[pid]/oom_adj will change the other with its + * scaled value.

+ * + * @return the oom_score_adj value for this process + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public int oom_score_adj() throws IOException { + return Integer.parseInt(read("oom_score_adj")); + } + + /** + *

/proc/[pid]/stat

+ *

+ *

Status information about the process. This is used by ps(1). It is defined in the kernel + * source file fs/proc/array.c.

+ *

+ *

The fields, in order, with their proper scanf(3) format specifiers, are:

+ *

+ *

    + *

    + *

  1. pid %d The process ID.
  2. + *

    + *

  3. comm %s The filename of the executable, in parentheses. This is visible whether or not + * the executable is swapped out.
  4. + *

    + *

  5. state %c One of the following characters, indicating process state: + *
      + *
    • R Running
    • + *
    • S Sleeping in an interruptible wait
    • + *
    • D Waiting in uninterruptible disk sleep
    • + *
    • Z Zombie
    • + *
    • T Stopped (on a signal) or (before Linux 2.6.33) trace stopped
    • + *
    • t Tracing stop (Linux 2.6.33 onward)
    • + *
    • W Paging (only before Linux 2.6.0)
    • + *
    • X Dead (from Linux 2.6.0 onward)
    • + *
    • x Dead (Linux 2.6.33 to 3.13 only)
    • + *
    • K Wakekill (Linux 2.6.33 to 3.13 only)
    • + *
    • W Waking (Linux 2.6.33 to 3.13 only)
    • + *
    • P Parked (Linux 3.9 to 3.13 only)
    • + *
    + *
  6. + *

    + *

  7. ppid %d The PID of the parent of this process.
  8. + *

    + *

  9. pgrp %d The process group ID of the process.
  10. + *

    + *

  11. session %d The session ID of the process.
  12. + *

    + *

  13. tty_nr %d The controlling terminal of the process. (The minor device number is contained + * in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.) + *
  14. + *

    + *

  15. tpgid %d The ID of the foreground process group of the controlling terminal of the + * process.
  16. + *

    + *

  17. flags %u The kernel flags word of the process. For bit meanings, see the PF_* defines in + * the Linux kernel source file include/linux/sched.h. Details depend on the kernel version. + * The format for this field was %lu before Linux 2.6.
  18. + *

    + *

  19. minflt %lu The number of minor faults the process has made which have not required + * loading a memory page from disk.
  20. + *

    + *

  21. cminflt %lu The number of minor faults that the process's waited-for children have + * made
  22. + *

    + *

  23. majflt %lu The number of major faults the process has made which have required loading a + * memory page from disk.
  24. + *

    + *

  25. cmajflt %lu The number of major faults that the process's waited-for children have + * made
  26. + *

    + *

  27. utime %lu Amount of time that this process has been scheduled in user mode, measured in + * clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time + * spent running a virtual CPU, see below), so that applications that are not aware of the guest + * time field do not lose that time from their calculations.
  28. + *

    + *

  29. stime %lu Amount of time that this process has been scheduled in kernel mode, measured + * in clock ticks (divide by sysconf(_SC_CLK_TCK)).
  30. + *

    + *

  31. cutime %ld Amount of time that this process's waited-for children have been scheduled in + * user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (See also times(2).) + * This includes guest time, cguest_time (time spent running a virtual CPU, see below).
  32. + *

    + *

  33. cstime %ld Amount of time that this process's waited-for children have been scheduled in + * kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
  34. + *

    + *

  35. priority %ld (Explanation for Linux 2.6) For processes running a real-time scheduling + * policy (policy below; see sched_setscheduler(2)), this is the negated scheduling priority, + * minus one; that is, a number in the range -2 to -100, corresponding to real-time priorities 1 + * to 99. For processes running under a non-real-time scheduling policy, this is the raw nice + * value (setpriority(2)) as represented in the kernel. The kernel stores nice values as numbers + * in the range 0 (high) to 39 (low), corresponding to the user-visible nice range of -20 to 19. + * Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this + * process
  36. + *

    + *

  37. nice %ld The nice value (see setpriority(2)), a value in the range 19 (low priority) to + * -20 (high priority).
  38. + *

    + *

  39. num_threads %ld Number of threads in this process (since Linux 2.6). Before kernel 2.6, + * this field was hard coded to 0 as a placeholder for an earlier removed field.
  40. + *

    + *

  41. itrealvalue %ld The time in jiffies before the next SIGALRM is sent to the process due + * to an interval timer. Since kernel 2.6.17, this field is no longer maintained, and is hard + * coded as 0.
  42. + *

    + *

  43. starttime %llu The time the process started after system boot. In kernels before Linux + * 2.6, this value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock + * ticks (divide by sysconf(_SC_CLK_TCK)).
  44. + *

    + *

  45. The format for this field was %lu before Linux 2.6. (23) vsize %lu Virtual memory size + * in bytes.
  46. + *

    + *

  47. rss %ld Resident Set Size: number of pages the process has in real memory. This is just + * the pages which count toward text, data, or stack space. This does not include pages which + * have not been demand-loaded in, or which are swapped out.
  48. + *

    + *

  49. rsslim %lu Current soft limit in bytes on the rss of the process; see the description of + * RLIMIT_RSS in getrlimit(2).
  50. + *

    + *

  51. startcode %lu The address above which program text can run.
  52. + *

    + *

  53. endcode %lu The address below which program text can run.
  54. + *

    + *

  55. startstack %lu The address of the start (i.e., bottom) of the stack.
  56. + *

    + *

  57. kstkesp %lu The current value of ESP (stack pointer), as found in the kernel stack page + * for the process.
  58. + *

    + *

  59. kstkeip %lu The current EIP (instruction pointer).
  60. + *

    + *

  61. signal %lu The bitmap of pending signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead
  62. + *

    + *

  63. blocked %lu The bitmap of blocked signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead
  64. + *

    + *

  65. sigignore %lu The bitmap of ignored signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead
  66. + *

    + *

  67. sigcatch %lu The bitmap of caught signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead.
  68. + *

    + *

  69. wchan %lu This is the "channel" in which the process is waiting. It is the address of a + * location in the kernel where the process is sleeping. The corresponding symbolic name can be + * found in /proc/[pid]/wchan.
  70. + *

    + *

  71. nswap %lu Number of pages swapped (not maintained).
  72. + *

    + *

  73. cnswap %lu Cumulative nswap for child processes (not maintained).
  74. + *

    + *

  75. exit_signal %d (since Linux 2.1.22) Signal to be sent to parent when we die.
  76. + *

    + *

  77. processor %d (since Linux 2.2.8) CPU number last executed on.
  78. + *

    + *

  79. rt_priority %u (since Linux 2.5.19) Real-time scheduling priority, a number in the + * range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time + * processes (see sched_setscheduler(2)).
  80. + *

    + *

  81. policy %u (since Linux 2.5.19) Scheduling policy (see sched_setscheduler(2)). Decode + * using the SCHED_* constants in linux/sched.h. The format for this field was %lu before Linux + * 2.6.22.
  82. + *

    + *

  83. delayacct_blkio_ticks %llu (since Linux 2.6.18) Aggregated block I/O delays, measured + * in clock ticks (centiseconds).
  84. + *

    + *

  85. guest_time %lu (since Linux 2.6.24) Guest time of the process (time spent running a + * virtual CPU for a guest operating system), measured in clock ticks (divide by + * sysconf(_SC_CLK_TCK)).
  86. + *

    + *

  87. cguest_time %ld (since Linux 2.6.24) Guest time of the process's children, measured in + * clock ticks (divide by sysconf(_SC_CLK_TCK)).
  88. + *

    + *

  89. start_data %lu (since Linux 3.3) Address above which program initialized and + * uninitialized (BSS) data are placed.
  90. + *

    + *

  91. end_data %lu (since Linux 3.3) Address below which program initialized and + * uninitialized (BSS) data are placed.
  92. + *

    + *

  93. start_brk %lu (since Linux 3.3) Address above which program heap can be expanded with + * brk(2).
  94. + *

    + *

  95. arg_start %lu (since Linux 3.5) Address above which program command-line arguments + * (argv) are placed.
  96. + *

    + *

  97. arg_end %lu (since Linux 3.5) Address below program command-line arguments (argv) are + * placed.
  98. + *

    + *

  99. env_start %lu (since Linux 3.5) Address above which program environment is placed.
  100. + *

    + *

  101. env_end %lu (since Linux 3.5) Address below which program environment is placed.
  102. + *

    + *

  103. exit_code %d (since Linux 3.5) The thread's exit status in the form reported by + * waitpid(2).
  104. + *

    + *

+ * + * @return the {@link Stat} for this process + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public Stat stat() throws IOException { + return Stat.get(pid); + } + + /** + *

Provides information about memory usage, measured in pages.

+ *

+ *

The columns are:

+ *

+ *

    + *
  • size (1) total program size (same as VmSize in /proc/[pid]/status)
  • + *
  • resident (2) resident set size (same as VmRSS in /proc/[pid]/status)
  • + *
  • share (3) shared pages (i.e., backed by a file)
  • + *
  • text (4) text (code)
  • + *
  • lib (5) library (unused in Linux 2.6)
  • + *
  • data (6) data + stack
  • + *
  • dt (7) dirty pages (unused in Linux 2.6)
  • + *
+ * + * @return the {@link Statm} for this process + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public Statm statm() throws IOException { + return Statm.get(pid); + } + + /** + *

/proc/[pid]/status

+ *

+ *

Provides much of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format + * that's + * easier for humans to parse.

+ *

+ *

Here's an example:

+ *

+ *

+     * $ cat /proc/$$/status
+     * Name:   bash
+     * State:  S (sleeping)
+     * Tgid:   3515
+     * Pid:    3515
+     * PPid:   3452
+     * TracerPid:      0
+     * Uid:    1000    1000    1000    1000
+     * Gid:    100     100     100     100
+     * FDSize: 256
+     * Groups: 16 33 100
+     * VmPeak:     9136 kB
+     * VmSize:     7896 kB
+     * VmLck:         0 kB
+     * VmPin:         0 kB
+     * VmHWM:      7572 kB
+     * VmRSS:      6316 kB
+     * VmData:     5224 kB
+     * VmStk:        88 kB
+     * VmExe:       572 kB
+     * VmLib:      1708 kB
+     * VmPMD:         4 kB
+     * VmPTE:        20 kB
+     * VmSwap:        0 kB
+     * Threads:        1
+     * SigQ:   0/3067
+     * SigPnd: 0000000000000000
+     * ShdPnd: 0000000000000000
+     * SigBlk: 0000000000010000
+     * SigIgn: 0000000000384004
+     * SigCgt: 000000004b813efb
+     * CapInh: 0000000000000000
+     * CapPrm: 0000000000000000
+     * CapEff: 0000000000000000
+     * CapBnd: ffffffffffffffff
+     * Seccomp:        0
+     * Cpus_allowed:   00000001
+     * Cpus_allowed_list:      0
+     * Mems_allowed:   1
+     * Mems_allowed_list:      0
+     * voluntary_ctxt_switches:        150
+     * nonvoluntary_ctxt_switches:     545
+     * 
+ *

+ *

The fields are as follows:

+ *

+ *

    + *
  1. Name: Command run by this process.
  2. + *
  3. State: Current state of the process. One of "R (running)", "S (sleeping)", "D (disk + * sleep)", + * "T (stopped)", "T (tracing stop)", "Z (zombie)", or "X (dead)".
  4. + *
  5. Tgid: Thread group ID (i.e., Process ID).
  6. + *
  7. Pid: Thread ID (see gettid(2)).
  8. + *
  9. PPid: PID of parent process.
  10. + *
  11. TracerPid: PID of process tracing this process (0 if not being traced).
  12. + *
  13. Uid, Gid: Real, effective, saved set, and filesystem UIDs (GIDs).
  14. + *
  15. FDSize: Number of file descriptor slots currently allocated.
  16. + *
  17. Groups: Supplementary group list.
  18. + *
  19. VmPeak: Peak virtual memory size.
  20. + *
  21. VmSize: Virtual memory size.
  22. + *
  23. VmLck: Locked memory size (see mlock(3)).
  24. + *
  25. VmPin: Pinned memory size (since Linux 3.2). These are pages that can't be moved because + * something needs to directly access physical memory.
  26. + *
  27. VmHWM: Peak resident set size ("high water mark").
  28. + *
  29. VmRSS: Resident set size.
  30. + *
  31. VmData, VmStk, VmExe: Size of data, stack, and text segments.
  32. + *
  33. VmLib: Shared library code size.
  34. + *
  35. VmPTE: Page table entries size (since Linux 2.6.10).
  36. + *
  37. VmPMD: Size of second-level page tables (since Linux 4.0).
  38. + *
  39. VmSwap: Swapped-out virtual memory size by anonymous private pages; shmem swap usage is + * not + * included (since Linux 2.6.34).
  40. + *
  41. Threads: Number of threads in process containing this thread.
  42. + *
  43. SigQ: This field contains two slash-separated numbers that relate to queued signals for + * the + * real user ID of this process. The first of these is the number of currently queued signals + * for + * this real user ID, and the second is the resource limit on the number of queued signals for + * this + * process (see the description of RLIMIT_SIGPENDING in getrlimit(2)).
  44. + *
  45. SigPnd, ShdPnd: Number of signals pending for thread and for process as a whole (see + * pthreads(7) and signal(7)).
  46. + *
  47. SigBlk, SigIgn, SigCgt: Masks indicating signals being blocked, ignored, and caught (see + * signal(7)).
  48. + *
  49. CapInh, CapPrm, CapEff: Masks of capabilities enabled in inheritable, permitted, and + * effective sets (see capabilities(7)).
  50. + *
  51. CapBnd: Capability Bounding set (since Linux 2.6.26, see capabilities(7)).
  52. + *
  53. Seccomp: Seccomp mode of the process (since Linux 3.8, see seccomp(2)). 0 means + * SECCOMP_MODE_DISABLED; 1 means SECCOMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field is + * provided only if the kernel was built with the CONFIG_SECCOMP kernel configuration option + * enabled.
  54. + *
  55. Cpus_allowed: Mask of CPUs on which this process may run (since Linux 2.6.24, see + * cpuset(7)).
  56. + *
  57. Cpus_allowed_list: Same as previous, but in "list format" (since Linux 2.6.26, see + * cpuset(7)).
  58. + *
  59. Mems_allowed: Mask of memory nodes allowed to this process (since Linux 2.6.24, see + * cpuset(7)).
  60. + *
  61. Mems_allowed_list: Same as previous, but in "list format" (since Linux 2.6.26, see + * cpuset(7)). + * voluntary_ctxt_switches, nonvoluntary_ctxt_switches: Number of voluntary and involuntary + * context + * switches (since Linux 2.6.23).
  62. + *
+ * + * @return the {@link Status} for this process + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public Status status() throws IOException { + return Status.get(pid); + } + + /** + * The symbolic name corresponding to the location in the kernel where the process is sleeping. + * + * @return the contents of /proc/[pid]/wchan + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public String wchan() throws IOException { + return read("wchan"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + dest.writeInt(this.pid); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Cgroup.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Cgroup.java new file mode 100755 index 00000000..a6850023 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Cgroup.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; + +import java.io.IOException; +import java.util.ArrayList; + +/** + *

/proc/[pid]/cgroup (since Linux 2.6.24)

+ *

+ *

This file describes control groups to which the process/task belongs. For each cgroup + * hierarchy there is one entry containing colon-separated fields of the form:

+ *

+ *

5:cpuacct,cpu,cpuset:/daemons

+ *

+ *

The colon-separated fields are, from left to right:

+ *

+ *

    + *
  1. hierarchy ID number
  2. + *
  3. set of subsystems bound to the hierarchy
  4. + *
  5. control group in the hierarchy to which the process belongs
  6. + *
+ *

+ *

This file is present only if the CONFIG_CGROUPS kernel configuration option is enabled.

+ * + * @see ControlGroup + */ +public final class Cgroup extends ProcFile { + + public static final Creator CREATOR = new Creator() { + + @Override + public Cgroup createFromParcel(Parcel source) { + return new Cgroup(source); + } + + @Override + public Cgroup[] newArray(int size) { + return new Cgroup[size]; + } + }; + /** + * the process' control groups + */ + public final ArrayList groups; + + private Cgroup(String path) throws IOException { + super(path); + String[] lines = content.split("\n"); + groups = new ArrayList<>(); + for (String line : lines) { + try { + groups.add(new ControlGroup(line)); + } catch (Exception ignored) { + } + } + } + + private Cgroup(Parcel in) { + super(in); + this.groups = in.createTypedArrayList(ControlGroup.CREATOR); + } + + /** + * Read /proc/[pid]/cgroup. + * + * @param pid the processes id. + * @return the {@link Cgroup} + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public static Cgroup get(int pid) throws IOException { + return new Cgroup(String.format("/proc/%d/cgroup", pid)); + } + + public ControlGroup getGroup(String subsystem) { + for (ControlGroup group : groups) { + String[] systems = group.subsystems.split(","); + for (String name : systems) { + if (name.equals(subsystem)) { + return group; + } + } + } + return null; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeTypedList(groups); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ControlGroup.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ControlGroup.java new file mode 100755 index 00000000..c114e9e7 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ControlGroup.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; +import android.os.Parcelable; + +public class ControlGroup implements Parcelable { + + public static final Creator CREATOR = new Creator() { + + @Override + public ControlGroup createFromParcel(Parcel source) { + return new ControlGroup(source); + } + + @Override + public ControlGroup[] newArray(int size) { + return new ControlGroup[size]; + } + }; + /** + * hierarchy ID number + */ + public final int id; + /** + * set of subsystems bound to the hierarchy + */ + public final String subsystems; + /** + * control group in the hierarchy to which the process belongs + */ + public final String group; + + protected ControlGroup(String line) throws NumberFormatException, IndexOutOfBoundsException { + String[] fields = line.split(":"); + id = Integer.parseInt(fields[0]); + subsystems = fields[1]; + group = fields[2]; + } + + protected ControlGroup(Parcel in) { + this.id = in.readInt(); + this.subsystems = in.readString(); + this.group = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.id); + dest.writeString(this.subsystems); + dest.writeString(this.group); + } + + @Override + public String toString() { + return String.format("%d:%s:%s", id, subsystems, group); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ProcFile.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ProcFile.java new file mode 100755 index 00000000..c03e5de2 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/ProcFile.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class ProcFile extends File implements Parcelable { + + public static final Creator CREATOR = new Creator() { + + @Override + public ProcFile createFromParcel(Parcel in) { + return new ProcFile(in); + } + + @Override + public ProcFile[] newArray(int size) { + return new ProcFile[size]; + } + }; + public final String content; + + protected ProcFile(String path) throws IOException { + super(path); + content = readFile(path); + } + + protected ProcFile(Parcel in) { + super(in.readString()); + this.content = in.readString(); + } + + /** + * Read the contents of a file. + * + * @param path the absolute path to the file. + * @return the contents of the file. + * @throws IOException if an error occurred while reading. + */ + protected static String readFile(String path) throws IOException { + BufferedReader reader = null; + try { + StringBuilder output = new StringBuilder(); + reader = new BufferedReader(new FileReader(path)); + for (String line = reader.readLine(), newLine = ""; line != null; line = reader.readLine()) { + output.append(newLine).append(line); + newLine = "\n"; + } + return output.toString(); + } finally { + if (reader != null) { + reader.close(); + } + } + } + + @Override + public long length() { + return content.length(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getAbsolutePath()); + dest.writeString(this.content); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Stat.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Stat.java new file mode 100755 index 00000000..f251ef46 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Stat.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; + +import java.io.IOException; + +/** + *

/proc/[pid]/stat

+ *

+ *

Status information about the process. This is used by ps(1). It is defined in the kernel + * source file fs/proc/array.c.

+ *

+ *

The fields, in order, with their proper scanf(3) format specifiers, are:

+ *

+ *

    + *
  1. pid %d The process ID.
  2. + *
  3. comm %s The filename of the executable, in parentheses. This is visible whether or not + * the executable is swapped out.
  4. + *
  5. state %c One of the following characters, indicating process state: + *
      + *
    • R Running
    • + *
    • S Sleeping in an interruptible wait
    • + *
    • D Waiting in uninterruptible disk sleep
    • + *
    • Z Zombie
    • + *
    • T Stopped (on a signal) or (before Linux 2.6.33) trace stopped
    • + *
    • t Tracing stop (Linux 2.6.33 onward)
    • + *
    • W Paging (only before Linux 2.6.0)
    • + *
    • X Dead (from Linux 2.6.0 onward)
    • + *
    • x Dead (Linux 2.6.33 to 3.13 only)
    • + *
    • K Wakekill (Linux 2.6.33 to 3.13 only)
    • + *
    • W Waking (Linux 2.6.33 to 3.13 only)
    • + *
    • P Parked (Linux 3.9 to 3.13 only)
    • + *
    + *
  6. + *
  7. ppid %d The PID of the parent of this process.
  8. + *
  9. pgrp %d The process group ID of the process.
  10. + *
  11. session %d The session ID of the process.
  12. + *
  13. tty_nr %d The controlling terminal of the process. (The minor device number is contained + * in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.) + *
  14. + *
  15. tpgid %d The ID of the foreground process group of the controlling terminal of the + * process.
  16. + *
  17. flags %u The kernel flags word of the process. For bit meanings, see the PF_* defines in + * the Linux kernel source file include/linux/sched.h. Details depend on the kernel version. + * The format for this field was %lu before Linux 2.6.
  18. + *
  19. minflt %lu The number of minor faults the process has made which have not required + * loading a memory page from disk.
  20. + *
  21. cminflt %lu The number of minor faults that the process's waited-for children have + * made
  22. + *
  23. majflt %lu The number of major faults the process has made which have required loading a + * memory page from disk.
  24. + *
  25. cmajflt %lu The number of major faults that the process's waited-for children have + * made
  26. + *
  27. utime %lu Amount of time that this process has been scheduled in user mode, measured in + * clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time + * spent running a virtual CPU, see below), so that applications that are not aware of the guest + * time field do not lose that time from their calculations.
  28. + *
  29. stime %lu Amount of time that this process has been scheduled in kernel mode, measured + * in clock ticks (divide by sysconf(_SC_CLK_TCK)).
  30. + *
  31. cutime %ld Amount of time that this process's waited-for children have been scheduled in + * user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (See also times(2).) + * This includes guest time, cguest_time (time spent running a virtual CPU, see below).
  32. + *
  33. cstime %ld Amount of time that this process's waited-for children have been scheduled in + * kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
  34. + *
  35. priority %ld (Explanation for Linux 2.6) For processes running a real-time scheduling + * policy (policy below; see sched_setscheduler(2)), this is the negated scheduling priority, + * minus one; that is, a number in the range -2 to -100, corresponding to real-time priorities 1 + * to 99. For processes running under a non-real-time scheduling policy, this is the raw nice + * value (setpriority(2)) as represented in the kernel. The kernel stores nice values as numbers + * in the range 0 (high) to 39 (low), corresponding to the user-visible nice range of -20 to 19. + * Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this + * process
  36. + *
  37. nice %ld The nice value (see setpriority(2)), a value in the range 19 (low priority) to + * -20 (high priority).
  38. + *
  39. num_threads %ld Number of threads in this process (since Linux 2.6). Before kernel 2.6, + * this field was hard coded to 0 as a placeholder for an earlier removed field.
  40. + *
  41. itrealvalue %ld The time in jiffies before the next SIGALRM is sent to the process due + * to an interval timer. Since kernel 2.6.17, this field is no longer maintained, and is hard + * coded as 0.
  42. + *
  43. starttime %llu The time the process started after system boot. In kernels before Linux + * 2.6, this value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock + * ticks (divide by sysconf(_SC_CLK_TCK)).
  44. + *
  45. The format for this field was %lu before Linux 2.6. (23) vsize %lu Virtual memory size + * in bytes.
  46. + *
  47. rss %ld Resident Set Size: number of pages the process has in real memory. This is just + * the pages which count toward text, data, or stack space. This does not include pages which + * have not been demand-loaded in, or which are swapped out.
  48. + *
  49. rsslim %lu Current soft limit in bytes on the rss of the process; see the description of + * RLIMIT_RSS in getrlimit(2).
  50. + *
  51. startcode %lu The address above which program text can run.
  52. + *
  53. endcode %lu The address below which program text can run.
  54. + *
  55. startstack %lu The address of the start (i.e., bottom) of the stack.
  56. + *
  57. kstkesp %lu The current value of ESP (stack pointer), as found in the kernel stack page + * for the process.
  58. + *
  59. kstkeip %lu The current EIP (instruction pointer).
  60. + *
  61. signal %lu The bitmap of pending signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead
  62. + *
  63. blocked %lu The bitmap of blocked signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead
  64. + *
  65. sigignore %lu The bitmap of ignored signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead
  66. + *
  67. sigcatch %lu The bitmap of caught signals, displayed as a decimal number. Obsolete, + * because it does not provide information on real-time signals; use /proc/[pid]/status + * instead.
  68. + *
  69. wchan %lu This is the "channel" in which the process is waiting. It is the address of a + * location in the kernel where the process is sleeping. The corresponding symbolic name can be + * found in /proc/[pid]/wchan.
  70. + *
  71. nswap %lu Number of pages swapped (not maintained).
  72. + *
  73. cnswap %lu Cumulative nswap for child processes (not maintained).
  74. + *
  75. exit_signal %d (since Linux 2.1.22) Signal to be sent to parent when we die.
  76. + *
  77. processor %d (since Linux 2.2.8) CPU number last executed on.
  78. + *
  79. rt_priority %u (since Linux 2.5.19) Real-time scheduling priority, a number in the + * range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time + * processes (see sched_setscheduler(2)).
  80. + *
  81. policy %u (since Linux 2.5.19) Scheduling policy (see sched_setscheduler(2)). Decode + * using the SCHED_* constants in linux/sched.h. The format for this field was %lu before Linux + * 2.6.22.
  82. + *
  83. delayacct_blkio_ticks %llu (since Linux 2.6.18) Aggregated block I/O delays, measured + * in clock ticks (centiseconds).
  84. + *
  85. guest_time %lu (since Linux 2.6.24) Guest time of the process (time spent running a + * virtual CPU for a guest operating system), measured in clock ticks (divide by + * sysconf(_SC_CLK_TCK)).
  86. + *
  87. cguest_time %ld (since Linux 2.6.24) Guest time of the process's children, measured in + * clock ticks (divide by sysconf(_SC_CLK_TCK)).
  88. + *
  89. start_data %lu (since Linux 3.3) Address above which program initialized and + * uninitialized (BSS) data are placed.
  90. + *
  91. end_data %lu (since Linux 3.3) Address below which program initialized and + * uninitialized (BSS) data are placed.
  92. + *
  93. start_brk %lu (since Linux 3.3) Address above which program heap can be expanded with + * brk(2).
  94. + *
  95. arg_start %lu (since Linux 3.5) Address above which program command-line arguments + * (argv) are placed.
  96. + *
  97. arg_end %lu (since Linux 3.5) Address below program command-line arguments (argv) are + * placed.
  98. + *
  99. env_start %lu (since Linux 3.5) Address above which program environment is placed.
  100. + *
  101. env_end %lu (since Linux 3.5) Address below which program environment is placed.
  102. + *
  103. exit_code %d (since Linux 3.5) The thread's exit status in the form reported by + * waitpid(2).
  104. + *
+ */ +public final class Stat extends ProcFile { + + public static final Creator CREATOR = new Creator() { + + @Override + public Stat createFromParcel(Parcel source) { + return new Stat(source); + } + + @Override + public Stat[] newArray(int size) { + return new Stat[size]; + } + }; + private final String[] fields; + + private Stat(String path) throws IOException { + super(path); + fields = content.split("\\s+"); + } + + private Stat(Parcel in) { + super(in); + this.fields = in.createStringArray(); + } + + /** + * Read /proc/[pid]/stat. + * + * @param pid the process id. + * @return the {@link Stat} + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public static Stat get(int pid) throws IOException { + return new Stat(String.format("/proc/%d/stat", pid)); + } + + /** + * The process ID. + */ + public int getPid() { + return Integer.parseInt(fields[0]); + } + + /** + * The filename of the executable, in parentheses. This is visible whether or not the + * executable is swapped out. + */ + public String getComm() { + return fields[1].replace("(", "").replace(")", ""); + } + + /** + *

One of the following characters, indicating process state:

+ *

+ *

    + *
  • 'R' Running
  • + *
  • 'S' Sleeping in an interruptible wait
  • + *
  • 'D' Waiting in uninterruptible disk sleep
  • + *
  • 'Z' Zombie
  • + *
  • 'T' Stopped (on a signal) or (before Linux 2.6.33) trace stopped
  • + *
  • 't' Tracing stop (Linux 2.6.33 onward)
  • + *
  • 'W' Paging (only before Linux 2.6.0)
  • + *
  • 'X' Dead (from Linux 2.6.0 onward)
  • + *
  • 'x' Dead (Linux 2.6.33 to 3.13 only)
  • + *
  • 'K' Wakekill (Linux 2.6.33 to 3.13 only)
  • + *
  • 'W' Waking (Linux 2.6.33 to 3.13 only)
  • + *
  • 'P' Parked (Linux 3.9 to 3.13 only)
  • + *
+ */ + public char state() { + return fields[2].charAt(0); + } + + /** + * The PID of the parent of this process. + */ + public int ppid() { + return Integer.parseInt(fields[3]); + } + + /** + * The process group ID of the process. + */ + public int pgrp() { + return Integer.parseInt(fields[4]); + } + + /** + * The session ID of the process. + */ + public int session() { + return Integer.parseInt(fields[5]); + } + + /** + * The controlling terminal of the process. (The minor device number is contained in the + * combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.) + */ + public int tty_nr() { + return Integer.parseInt(fields[6]); + } + + /** + * The ID of the foreground process group of the controlling terminal of the process. + */ + public int tpgid() { + return Integer.parseInt(fields[7]); + } + + /** + *

The kernel flags word of the process. For bit meanings, see the PF_* defines in the Linux + * kernel source file include/linux/sched.h. Details depend on the kernel version.

+ *

+ *

The format for this field was %lu before Linux 2.6.

+ */ + public int flags() { + return Integer.parseInt(fields[8]); + } + + /** + * The number of minor faults the process has made which have not required loading a memory + * page from disk. + */ + public long minflt() { + return Long.parseLong(fields[9]); + } + + /** + * The number of minor faults that the process's waited-for children have made. + */ + public long cminflt() { + return Long.parseLong(fields[10]); + } + + /** + * The number of major faults the process has made which have required loading a memory page + * from disk. + */ + public long majflt() { + return Long.parseLong(fields[11]); + } + + /** + * The number of major faults that the process's waited-for children have made. + */ + public long cmajflt() { + return Long.parseLong(fields[12]); + } + + /** + * Amount of time that this process has been scheduled in user mode, measured in clock ticks + * (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent running + * a virtual CPU, see below), so that applications that are not aware of the guest time field + * do not lose that time from their calculations. + */ + public long utime() { + return Long.parseLong(fields[13]); + } + + /** + * Amount of time that this process has been scheduled in kernel mode, measured in clock ticks + * (divide by sysconf(_SC_CLK_TCK)). + */ + public long stime() { + return Long.parseLong(fields[14]); + } + + /** + * Amount of time that this process's waited-for children have been scheduled in user mode, + * measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (See also times(2).) This + * includes guest time, cguest_time (time spent running a virtual CPU, see below). + */ + public long cutime() { + return Long.parseLong(fields[15]); + } + + /** + * Amount of time that this process's waited-for children have been scheduled in kernel mode, + * measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). + */ + public long cstime() { + return Long.parseLong(fields[16]); + } + + /** + *

(Explanation for Linux 2.6) For processes running a real-time scheduling policy (policy + * below; see sched_setscheduler(2)), this is the negated scheduling priority, minus one; that + * is, + * a number in the range -2 to -100, corresponding to real-time priorities 1 to 99. For + * processes + * running under a non-real-time scheduling policy, this is the raw nice value (setpriority(2)) + * as + * represented in the kernel. The kernel stores nice values as numbers in the range 0 (high) to + * 39 (low), corresponding to the user-visible nice range of -20 to 19.

+ *

+ *

Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this + * process.

+ */ + public long priority() { + return Long.parseLong(fields[17]); + } + + /** + * The nice value (see setpriority(2)), a value in the range 19 (low priority) to -20 (high + * priority). + */ + public int nice() { + return Integer.parseInt(fields[18]); + } + + /** + * Number of threads in this process (since Linux 2.6). Before kernel 2.6, this field was hard + * coded to 0 as a placeholder for an earlier removed field. + */ + public long num_threads() { + return Long.parseLong(fields[19]); + } + + /** + * The time in jiffies before the next SIGALRM is sent to the process due to an interval timer. + * Since kernel 2.6.17, this field is no longer maintained, and is hard coded as 0. + */ + public long itrealvalue() { + return Long.parseLong(fields[20]); + } + + /** + *

The time the process started after system boot. In kernels before Linux 2.6, this value was + * expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks (divide by + * sysconf(_SC_CLK_TCK)).

+ *

+ *

The format for this field was %lu before Linux 2.6.

+ */ + public long starttime() { + return Long.parseLong(fields[21]); + } + + /** + * Virtual memory size in bytes. + */ + public long vsize() { + return Long.parseLong(fields[22]); + } + + /** + * Resident Set Size: number of pages the process has in real memory. This is just the pages + * which count toward text, data, or stack space. This does not include pages which have not + * been demand-loaded in, or which are swapped out. + */ + public long rss() { + return Long.parseLong(fields[23]); + } + + /** + * Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in + * getrlimit(2). + */ + public long rsslim() { + return Long.parseLong(fields[24]); + } + + /** + * The address above which program text can run. + */ + public long startcode() { + return Long.parseLong(fields[25]); + } + + /** + * The address below which program text can run. + */ + public long endcode() { + return Long.parseLong(fields[26]); + } + + /** + * The address of the start (i.e., bottom) of the stack. + */ + public long startstack() { + return Long.parseLong(fields[27]); + } + + /** + * The current value of ESP (stack pointer), as found in the kernel stack page for the process. + */ + public long kstkesp() { + return Long.parseLong(fields[28]); + } + + /** + * The current EIP (instruction pointer). + */ + public long kstkeip() { + return Long.parseLong(fields[29]); + } + + /** + * The bitmap of pending signals, displayed as a decimal number. Obsolete, because it does not + * provide information on real-time signals; use /proc/[pid]/status instead. + */ + public long signal() { + return Long.parseLong(fields[30]); + } + + /** + * The bitmap of blocked signals, displayed as a decimal number. Obsolete, because it does not + * provide information on real-time signals; use /proc/[pid]/status instead. + */ + public long blocked() { + return Long.parseLong(fields[31]); + } + + /** + * The bitmap of ignored signals, displayed as a decimal number. Obsolete, because it does not + * provide information on real-time signals; use /proc/[pid]/status instead. + */ + public long sigignore() { + return Long.parseLong(fields[32]); + } + + /** + * The bitmap of caught signals, displayed as a decimal number. Obsolete, because it does not + * provide information on real-time signals; use /proc/[pid]/status instead. + */ + public long sigcatch() { + return Long.parseLong(fields[33]); + } + + /** + * This is the "channel" in which the process is waiting. It is the address of a location in the + * kernel where the process is sleeping. The corresponding symbolic name can be found in + * /proc/[pid]/wchan. + */ + public long wchan() { + return Long.parseLong(fields[34]); + } + + /** + * Number of pages swapped (not maintained). + */ + public long nswap() { + return Long.parseLong(fields[35]); + } + + /** + * Cumulative nswap for child processes (not maintained). + */ + public long cnswap() { + return Long.parseLong(fields[36]); + } + + /** + * (since Linux 2.1.22) + * Signal to be sent to parent when we die. + */ + public int exit_signal() { + return Integer.parseInt(fields[37]); + } + + /** + * (since Linux 2.2.8) + * CPU number last executed on. + */ + public int processor() { + return Integer.parseInt(fields[38]); + } + + /** + * (since Linux 2.5.19) + * Real-time scheduling priority, a number in the range 1 to 99 for processes scheduled under a + * real-time policy, or 0, for non-real-time processes (see sched_setscheduler(2)). + */ + public int rt_priority() { + return Integer.parseInt(fields[39]); + } + + /** + *

(since Linux 2.5.19) Scheduling policy (see sched_setscheduler(2)). Decode using the + * SCHED_* + * constants in linux/sched.h.

+ *

+ *

The format for this field was %lu before Linux 2.6.22.

+ */ + public int policy() { + return Integer.parseInt(fields[40]); + } + + /** + * (since Linux 2.6.18) + * Aggregated block I/O delays, measured in clock ticks (centiseconds). + */ + public long delayacct_blkio_ticks() { + return Long.parseLong(fields[41]); + } + + /** + * (since Linux 2.6.24) + * Guest time of the process (time spent running a virtual CPU for a guest operating system), + * measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). + */ + public long guest_time() { + return Long.parseLong(fields[42]); + } + + /** + * (since Linux 2.6.24) + * Guest time of the process's children, measured in clock ticks (divide by + * sysconf(_SC_CLK_TCK)). + */ + public long cguest_time() { + return Long.parseLong(fields[43]); + } + + /** + * (since Linux 3.3) + * Address above which program initialized and uninitialized (BSS) data are placed. + */ + public long start_data() { + return Long.parseLong(fields[44]); + } + + /** + * (since Linux 3.3) + * Address below which program initialized and uninitialized (BSS) data are placed. + */ + public long end_data() { + return Long.parseLong(fields[45]); + } + + /** + * (since Linux 3.3) + * Address above which program heap can be expanded with brk(2). + */ + public long start_brk() { + return Long.parseLong(fields[46]); + } + + /** + * (since Linux 3.5) + * Address above which program command-line arguments (argv) are placed. + */ + public long arg_start() { + return Long.parseLong(fields[47]); + } + + /** + * (since Linux 3.5) + * Address below program command-line arguments (argv) are placed. + */ + public long arg_end() { + return Long.parseLong(fields[48]); + } + + /** + * (since Linux 3.5) + * Address above which program environment is placed. + */ + public long env_start() { + return Long.parseLong(fields[49]); + } + + /** + * (since Linux 3.5) + * Address below which program environment is placed. + */ + public long env_end() { + return Long.parseLong(fields[50]); + } + + /** + * (since Linux 3.5) + * The thread's exit status in the form reported by waitpid(2). + */ + public int exit_code() { + return Integer.parseInt(fields[51]); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeStringArray(fields); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Statm.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Statm.java new file mode 100755 index 00000000..d76c974a --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Statm.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; + +import java.io.IOException; + +/** + *

Provides information about memory usage, measured in pages.

+ *

+ *

The columns are:

+ *

+ *

    + *
  • size (1) total program size (same as VmSize in /proc/[pid]/status)
  • + *
  • resident (2) resident set size (same as VmRSS in /proc/[pid]/status)
  • + *
  • share (3) shared pages (i.e., backed by a file)
  • + *
  • text (4) text (code)
  • + *
  • lib (5) library (unused in Linux 2.6)
  • + *
  • data (6) data + stack
  • + *
  • dt (7) dirty pages (unused in Linux 2.6)
  • + *
+ */ +public final class Statm extends ProcFile { + + public static final Creator CREATOR = new Creator() { + + @Override + public Statm createFromParcel(Parcel source) { + return new Statm(source); + } + + @Override + public Statm[] newArray(int size) { + return new Statm[size]; + } + }; + public final String[] fields; + + private Statm(String path) throws IOException { + super(path); + fields = content.split("\\s+"); + } + + private Statm(Parcel in) { + super(in); + this.fields = in.createStringArray(); + } + + /** + * Read /proc/[pid]/statm. + * + * @param pid the process id. + * @return the {@link Statm} + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public static Statm get(int pid) throws IOException { + return new Statm(String.format("/proc/%d/statm", pid)); + } + + /** + * @return the total program size in bytes + */ + public long getSize() { + return Long.parseLong(fields[0]) * 1024; + } + + /** + * @return the resident set size in bytes + */ + public long getResidentSetSize() { + return Long.parseLong(fields[1]) * 1024; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeStringArray(this.fields); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Status.java b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Status.java new file mode 100755 index 00000000..35c59da6 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/utils/processmanager/models/Status.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015. Jared Rummler + * + * 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.kgurgul.cpuinfo.utils.processmanager.models; + +import android.os.Parcel; + +import java.io.IOException; + +/** + *

/proc/[pid]/status

+ *

+ *

Provides much of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format that's + * easier for humans to parse.

+ *

+ *

Here's an example:

+ *

+ *

+ * $ cat /proc/$$/status
+ * Name:   bash
+ * State:  S (sleeping)
+ * Tgid:   3515
+ * Pid:    3515
+ * PPid:   3452
+ * TracerPid:      0
+ * Uid:    1000    1000    1000    1000
+ * Gid:    100     100     100     100
+ * FDSize: 256
+ * Groups: 16 33 100
+ * VmPeak:     9136 kB
+ * VmSize:     7896 kB
+ * VmLck:         0 kB
+ * VmPin:         0 kB
+ * VmHWM:      7572 kB
+ * VmRSS:      6316 kB
+ * VmData:     5224 kB
+ * VmStk:        88 kB
+ * VmExe:       572 kB
+ * VmLib:      1708 kB
+ * VmPMD:         4 kB
+ * VmPTE:        20 kB
+ * VmSwap:        0 kB
+ * Threads:        1
+ * SigQ:   0/3067
+ * SigPnd: 0000000000000000
+ * ShdPnd: 0000000000000000
+ * SigBlk: 0000000000010000
+ * SigIgn: 0000000000384004
+ * SigCgt: 000000004b813efb
+ * CapInh: 0000000000000000
+ * CapPrm: 0000000000000000
+ * CapEff: 0000000000000000
+ * CapBnd: ffffffffffffffff
+ * Seccomp:        0
+ * Cpus_allowed:   00000001
+ * Cpus_allowed_list:      0
+ * Mems_allowed:   1
+ * Mems_allowed_list:      0
+ * voluntary_ctxt_switches:        150
+ * nonvoluntary_ctxt_switches:     545
+ * 
+ *

+ *

The fields are as follows:

+ *

+ *

    + *
  1. Name: Command run by this process.
  2. + *
  3. State: Current state of the process. One of "R (running)", "S (sleeping)", "D (disk + * sleep)", + * "T (stopped)", "T (tracing stop)", "Z (zombie)", or "X (dead)".
  4. + *
  5. Tgid: Thread group ID (i.e., Process ID).
  6. + *
  7. Pid: Thread ID (see gettid(2)).
  8. + *
  9. PPid: PID of parent process.
  10. + *
  11. TracerPid: PID of process tracing this process (0 if not being traced).
  12. + *
  13. Uid, Gid: Real, effective, saved set, and filesystem UIDs (GIDs).
  14. + *
  15. FDSize: Number of file descriptor slots currently allocated.
  16. + *
  17. Groups: Supplementary group list.
  18. + *
  19. VmPeak: Peak virtual memory size.
  20. + *
  21. VmSize: Virtual memory size.
  22. + *
  23. VmLck: Locked memory size (see mlock(3)).
  24. + *
  25. VmPin: Pinned memory size (since Linux 3.2). These are pages that can't be moved because + * something needs to directly access physical memory.
  26. + *
  27. VmHWM: Peak resident set size ("high water mark").
  28. + *
  29. VmRSS: Resident set size.
  30. + *
  31. VmData, VmStk, VmExe: Size of data, stack, and text segments.
  32. + *
  33. VmLib: Shared library code size.
  34. + *
  35. VmPTE: Page table entries size (since Linux 2.6.10).
  36. + *
  37. VmPMD: Size of second-level page tables (since Linux 4.0).
  38. + *
  39. VmSwap: Swapped-out virtual memory size by anonymous private pages; shmem swap usage is not + * included (since Linux 2.6.34).
  40. + *
  41. Threads: Number of threads in process containing this thread.
  42. + *
  43. SigQ: This field contains two slash-separated numbers that relate to queued signals for the + * real user ID of this process. The first of these is the number of currently queued signals for + * this real user ID, and the second is the resource limit on the number of queued signals for this + * process (see the description of RLIMIT_SIGPENDING in getrlimit(2)).
  44. + *
  45. SigPnd, ShdPnd: Number of signals pending for thread and for process as a whole (see + * pthreads(7) and signal(7)).
  46. + *
  47. SigBlk, SigIgn, SigCgt: Masks indicating signals being blocked, ignored, and caught (see + * signal(7)).
  48. + *
  49. CapInh, CapPrm, CapEff: Masks of capabilities enabled in inheritable, permitted, and + * effective sets (see capabilities(7)).
  50. + *
  51. CapBnd: Capability Bounding set (since Linux 2.6.26, see capabilities(7)).
  52. + *
  53. Seccomp: Seccomp mode of the process (since Linux 3.8, see seccomp(2)). 0 means + * SECCOMP_MODE_DISABLED; 1 means SECCOMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field is + * provided only if the kernel was built with the CONFIG_SECCOMP kernel configuration option + * enabled.
  54. + *
  55. Cpus_allowed: Mask of CPUs on which this process may run (since Linux 2.6.24, see + * cpuset(7)).
  56. + *
  57. Cpus_allowed_list: Same as previous, but in "list format" (since Linux 2.6.26, see + * cpuset(7)).
  58. + *
  59. Mems_allowed: Mask of memory nodes allowed to this process (since Linux 2.6.24, see + * cpuset(7)).
  60. + *
  61. Mems_allowed_list: Same as previous, but in "list format" (since Linux 2.6.26, see + * cpuset(7)). + * voluntary_ctxt_switches, nonvoluntary_ctxt_switches: Number of voluntary and involuntary context + * switches (since Linux 2.6.23).
  62. + *
+ */ +public final class Status extends ProcFile { + + public static final Creator CREATOR = new Creator() { + + @Override + public Status createFromParcel(Parcel source) { + return new Status(source); + } + + @Override + public Status[] newArray(int size) { + return new Status[size]; + } + }; + + private Status(String path) throws IOException { + super(path); + } + + private Status(Parcel in) { + super(in); + } + + /** + * Read /proc/[pid]/status. + * + * @param pid the process id. + * @return the {@link Status} + * @throws IOException if the file does not exist or we don't have read permissions. + */ + public static Status get(int pid) throws IOException { + return new Status(String.format("/proc/%d/status", pid)); + } + + /** + * Get the value of one of the fields. + * + * @param fieldName the field name. E.g "PPid", "Uid", "Groups". + * @return The value of the field or {@code null}. + */ + public String getValue(String fieldName) { + String[] lines = content.split("\n"); + for (String line : lines) { + if (line.startsWith(fieldName + ":")) { + return line.split(fieldName + ":")[1].trim(); + } + } + return null; + } + + /** + * @return The process' UID or -1 if parsing the UID failed. + */ + public int getUid() { + try { + return Integer.parseInt(getValue("Uid").split("\\s+")[0]); + } catch (Exception e) { + return -1; + } + } + + /** + * @return The process' GID or -1 if parsing the GID failed. + */ + public int getGid() { + try { + return Integer.parseInt(getValue("Gid").split("\\s+")[0]); + } catch (Exception e) { + return -1; + } + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/arc/ArcProgress.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/arc/ArcProgress.java new file mode 100755 index 00000000..eb2b5440 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/arc/ArcProgress.java @@ -0,0 +1,378 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.arc; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Bundle; +import android.os.Parcelable; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import com.kgurgul.cpuinfo.R; + +/** + * Created by bruce on 11/6/14. + */ +public class ArcProgress extends View { + private Paint paint; + protected Paint textPaint; + + private RectF rectF = new RectF(); + + private float strokeWidth; + private float suffixTextSize; + private float bottomTextSize; + private String bottomText; + private float textSize; + private int textColor; + private int progress = 0; + private int max; + private int finishedStrokeColor; + private int unfinishedStrokeColor; + private float arcAngle; + private String suffixText = "%"; + private float suffixTextPadding; + + private float arcBottomHeight; + + private final int default_finished_color = Color.rgb(83, 195, 255); + private final int default_unfinished_color = Color.rgb(19, 31, 37); + private final int default_text_color = Color.WHITE; + private final float default_suffix_text_size; + private final float default_suffix_padding; + private final float default_bottom_text_size; + private final float default_stroke_width; + private final String default_suffix_text; + private final int default_max = 100; + private final float default_arc_angle = 360 * 0.8f; + private float default_text_size; + private final int min_size; + private final int shadowWidth = 10; + + private static final String INSTANCE_STATE = "saved_instance"; + private static final String INSTANCE_STROKE_WIDTH = "stroke_width"; + private static final String INSTANCE_SUFFIX_TEXT_SIZE = "suffix_text_size"; + private static final String INSTANCE_SUFFIX_TEXT_PADDING = "suffix_text_padding"; + private static final String INSTANCE_BOTTOM_TEXT_SIZE = "bottom_text_size"; + private static final String INSTANCE_BOTTOM_TEXT = "bottom_text"; + private static final String INSTANCE_TEXT_SIZE = "text_size"; + private static final String INSTANCE_TEXT_COLOR = "text_color"; + private static final String INSTANCE_PROGRESS = "progress"; + private static final String INSTANCE_MAX = "max"; + private static final String INSTANCE_FINISHED_STROKE_COLOR = "finished_stroke_color"; + private static final String INSTANCE_UNFINISHED_STROKE_COLOR = "unfinished_stroke_color"; + private static final String INSTANCE_ARC_ANGLE = "arc_angle"; + private static final String INSTANCE_SUFFIX = "suffix"; + + public ArcProgress(Context context) { + this(context, null); + } + + public ArcProgress(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ArcProgress(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + min_size = (int) dp2px(getResources(), 100); + default_text_size = sp2px(getResources(), 40); + default_suffix_text_size = sp2px(getResources(), 15); + default_suffix_padding = dp2px(getResources(), 6); + default_suffix_text = "%"; + default_bottom_text_size = sp2px(getResources(), 22); + default_stroke_width = dp2px(getResources(), 10); + + TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcProgress, defStyleAttr, 0); + initByAttributes(attributes); + attributes.recycle(); + + initPainters(); + } + + protected void initByAttributes(TypedArray attributes) { + finishedStrokeColor = attributes.getColor(R.styleable.ArcProgress_arc_finished_color, default_finished_color); + unfinishedStrokeColor = attributes.getColor(R.styleable.ArcProgress_arc_unfinished_color, default_unfinished_color); + textColor = attributes.getColor(R.styleable.ArcProgress_arc_text_color, default_text_color); + textSize = attributes.getDimension(R.styleable.ArcProgress_arc_text_size, default_text_size); + arcAngle = attributes.getFloat(R.styleable.ArcProgress_arc_angle, default_arc_angle); + setMax(attributes.getInt(R.styleable.ArcProgress_arc_max, default_max)); + setProgress(attributes.getInt(R.styleable.ArcProgress_arc_progress, 0)); + strokeWidth = attributes.getDimension(R.styleable.ArcProgress_arc_stroke_width, default_stroke_width); + suffixTextSize = attributes.getDimension(R.styleable.ArcProgress_arc_suffix_text_size, default_suffix_text_size); + suffixText = TextUtils.isEmpty(attributes.getString(R.styleable.ArcProgress_arc_suffix_text)) ? default_suffix_text : attributes.getString(R.styleable.ArcProgress_arc_suffix_text); + suffixTextPadding = attributes.getDimension(R.styleable.ArcProgress_arc_suffix_text_padding, default_suffix_padding); + bottomTextSize = attributes.getDimension(R.styleable.ArcProgress_arc_bottom_text_size, default_bottom_text_size); + bottomText = attributes.getString(R.styleable.ArcProgress_arc_bottom_text); + } + + protected void initPainters() { + textPaint = new TextPaint(); + textPaint.setColor(textColor); + textPaint.setTextSize(textSize); + textPaint.setAntiAlias(true); + + paint = new Paint(); + paint.setColor(default_unfinished_color); + paint.setAntiAlias(true); + paint.setStrokeWidth(strokeWidth); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeCap(Paint.Cap.ROUND); + } + + @Override + public void invalidate() { + initPainters(); + super.invalidate(); + } + + public float getStrokeWidth() { + return strokeWidth; + } + + public void setStrokeWidth(float strokeWidth) { + this.strokeWidth = strokeWidth; + this.invalidate(); + } + + public float getSuffixTextSize() { + return suffixTextSize; + } + + public void setSuffixTextSize(float suffixTextSize) { + this.suffixTextSize = suffixTextSize; + this.invalidate(); + } + + public String getBottomText() { + return bottomText; + } + + public void setBottomText(String bottomText) { + this.bottomText = bottomText; + this.invalidate(); + } + + public int getProgress() { + return progress; + } + + public void setProgress(int progress) { + this.progress = progress; + if (this.progress > getMax()) { + this.progress %= getMax(); + } + invalidate(); + } + + public int getMax() { + return max; + } + + public void setMax(int max) { + if (max > 0) { + this.max = max; + invalidate(); + } + } + + public float getBottomTextSize() { + return bottomTextSize; + } + + public void setBottomTextSize(float bottomTextSize) { + this.bottomTextSize = bottomTextSize; + this.invalidate(); + } + + public float getTextSize() { + return textSize; + } + + public void setTextSize(float textSize) { + this.textSize = textSize; + this.invalidate(); + } + + public int getTextColor() { + return textColor; + } + + public void setTextColor(int textColor) { + this.textColor = textColor; + this.invalidate(); + } + + public int getFinishedStrokeColor() { + return finishedStrokeColor; + } + + public void setFinishedStrokeColor(int finishedStrokeColor) { + this.finishedStrokeColor = finishedStrokeColor; + this.invalidate(); + } + + public int getUnfinishedStrokeColor() { + return unfinishedStrokeColor; + } + + public void setUnfinishedStrokeColor(int unfinishedStrokeColor) { + this.unfinishedStrokeColor = unfinishedStrokeColor; + this.invalidate(); + } + + public float getArcAngle() { + return arcAngle; + } + + public void setArcAngle(float arcAngle) { + this.arcAngle = arcAngle; + this.invalidate(); + } + + public String getSuffixText() { + return suffixText; + } + + public void setSuffixText(String suffixText) { + this.suffixText = suffixText; + this.invalidate(); + } + + public float getSuffixTextPadding() { + return suffixTextPadding; + } + + public void setSuffixTextPadding(float suffixTextPadding) { + this.suffixTextPadding = suffixTextPadding; + this.invalidate(); + } + + @Override + protected int getSuggestedMinimumHeight() { + return min_size; + } + + @Override + protected int getSuggestedMinimumWidth() { + return min_size; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + rectF.set(strokeWidth / 2f + shadowWidth, strokeWidth / 2f + shadowWidth, + width - strokeWidth / 2f - shadowWidth, + MeasureSpec.getSize(heightMeasureSpec) - strokeWidth / 2f - shadowWidth); + float radius = width / 2f; + float angle = (360 - arcAngle) / 2f; + arcBottomHeight = radius * (float) (1 - Math.cos(angle / 180 * Math.PI)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + float startAngle = 270 - arcAngle / 2f; + float finishedSweepAngle = progress / (float) getMax() * arcAngle; + float finishedStartAngle = startAngle; + paint.setColor(unfinishedStrokeColor); + canvas.drawArc(rectF, startAngle, arcAngle, false, paint); + paint.setColor(finishedStrokeColor); + paint.setShadowLayer(shadowWidth, 0.0f, 2.0f, finishedStrokeColor); + canvas.drawArc(rectF, finishedStartAngle, finishedSweepAngle, false, paint); + + String text = String.valueOf(getProgress()); + if (!TextUtils.isEmpty(text)) { + textPaint.setColor(textColor); + textPaint.setTextSize(textSize); + float textHeight = textPaint.descent() + textPaint.ascent(); + float textBaseline = (getHeight() - textHeight) / 2.0f; + canvas.drawText(text, (getWidth() - textPaint.measureText(text)) / 2.0f, textBaseline, textPaint); + textPaint.setTextSize(suffixTextSize); + float suffixHeight = textPaint.descent() + textPaint.ascent(); + canvas.drawText(suffixText, getWidth() / 2.0f + textPaint.measureText(text) + suffixTextPadding, textBaseline + textHeight - suffixHeight, textPaint); + } + + if (!TextUtils.isEmpty(getBottomText())) { + textPaint.setTextSize(bottomTextSize); + float bottomTextBaseline = getHeight() - arcBottomHeight - (textPaint.descent() + textPaint.ascent()) / 2; + canvas.drawText(getBottomText(), (getWidth() - textPaint.measureText(getBottomText())) / 2.0f, bottomTextBaseline, textPaint); + } + } + + @Override + protected Parcelable onSaveInstanceState() { + final Bundle bundle = new Bundle(); + bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState()); + bundle.putFloat(INSTANCE_STROKE_WIDTH, getStrokeWidth()); + bundle.putFloat(INSTANCE_SUFFIX_TEXT_SIZE, getSuffixTextSize()); + bundle.putFloat(INSTANCE_SUFFIX_TEXT_PADDING, getSuffixTextPadding()); + bundle.putFloat(INSTANCE_BOTTOM_TEXT_SIZE, getBottomTextSize()); + bundle.putString(INSTANCE_BOTTOM_TEXT, getBottomText()); + bundle.putFloat(INSTANCE_TEXT_SIZE, getTextSize()); + bundle.putInt(INSTANCE_TEXT_COLOR, getTextColor()); + bundle.putInt(INSTANCE_PROGRESS, getProgress()); + bundle.putInt(INSTANCE_MAX, getMax()); + bundle.putInt(INSTANCE_FINISHED_STROKE_COLOR, getFinishedStrokeColor()); + bundle.putInt(INSTANCE_UNFINISHED_STROKE_COLOR, getUnfinishedStrokeColor()); + bundle.putFloat(INSTANCE_ARC_ANGLE, getArcAngle()); + bundle.putString(INSTANCE_SUFFIX, getSuffixText()); + return bundle; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + final Bundle bundle = (Bundle) state; + strokeWidth = bundle.getFloat(INSTANCE_STROKE_WIDTH); + suffixTextSize = bundle.getFloat(INSTANCE_SUFFIX_TEXT_SIZE); + suffixTextPadding = bundle.getFloat(INSTANCE_SUFFIX_TEXT_PADDING); + bottomTextSize = bundle.getFloat(INSTANCE_BOTTOM_TEXT_SIZE); + bottomText = bundle.getString(INSTANCE_BOTTOM_TEXT); + textSize = bundle.getFloat(INSTANCE_TEXT_SIZE); + textColor = bundle.getInt(INSTANCE_TEXT_COLOR); + setMax(bundle.getInt(INSTANCE_MAX)); + setProgress(bundle.getInt(INSTANCE_PROGRESS)); + finishedStrokeColor = bundle.getInt(INSTANCE_FINISHED_STROKE_COLOR); + unfinishedStrokeColor = bundle.getInt(INSTANCE_UNFINISHED_STROKE_COLOR); + suffixText = bundle.getString(INSTANCE_SUFFIX); + initPainters(); + super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE)); + return; + } + super.onRestoreInstanceState(state); + } + + public float dp2px(Resources resources, float dp) { + final float scale = resources.getDisplayMetrics().density; + return dp * scale + 0.5f; + } + + public float sp2px(Resources resources, float sp) { + final float scale = resources.getDisplayMetrics().scaledDensity; + return sp * scale; + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/BaseRoundCornerProgressBar.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/BaseRoundCornerProgressBar.java new file mode 100644 index 00000000..de837504 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/BaseRoundCornerProgressBar.java @@ -0,0 +1,386 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.progress; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.kgurgul.cpuinfo.R; + +/** + * Fork from https://github.com/akexorcist/Android-RoundCornerProgressBar with saved instance state + * fix + */ +public abstract class BaseRoundCornerProgressBar extends LinearLayout { + protected final static int DEFAULT_MAX_PROGRESS = 100; + protected final static int DEFAULT_PROGRESS = 0; + protected final static int DEFAULT_SECONDARY_PROGRESS = 0; + protected final static int DEFAULT_PROGRESS_RADIUS = 30; + protected final static int DEFAULT_BACKGROUND_PADDING = 0; + + private LinearLayout layoutBackground; + private LinearLayout layoutProgress; + private LinearLayout layoutSecondaryProgress; + + private int radius; + private int padding; + private int totalWidth; + + private float max; + private float progress; + private float secondaryProgress; + + private int colorBackground; + private int colorProgress; + private int colorSecondaryProgress; + + private boolean isReverse; + + private OnProgressChangedListener progressChangedListener; + + public BaseRoundCornerProgressBar(Context context, AttributeSet attrs) { + super(context, attrs); + if (isInEditMode()) { + previewLayout(context); + } else { + setup(context, attrs); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public BaseRoundCornerProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (isInEditMode()) { + previewLayout(context); + } else { + setup(context, attrs); + } + } + + private void previewLayout(Context context) { + setGravity(Gravity.CENTER); + TextView tv = new TextView(context); + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + tv.setLayoutParams(params); + tv.setGravity(Gravity.CENTER); + tv.setText(getClass().getSimpleName()); + tv.setTextColor(Color.WHITE); + tv.setBackgroundColor(Color.GRAY); + addView(tv); + } + + // Setup a progress bar layout by sub class + protected abstract int initLayout(); + + // Setup an attribute parameter on sub class + protected abstract void initStyleable(Context context, AttributeSet attrs); + + // Initial any view on sub class + protected abstract void initView(); + + // Draw a progress by sub class + protected abstract void drawProgress(LinearLayout layoutProgress, float max, float progress, float totalWidth, + int radius, int padding, int colorProgress, boolean isReverse); + + // Draw all view on sub class + protected abstract void onViewDraw(); + + public void setup(Context context, AttributeSet attrs) { + setupStyleable(context, attrs); + + removeAllViews(); + // Setup layout for sub class + LayoutInflater.from(context).inflate(initLayout(), this); + // Initial default view + layoutBackground = (LinearLayout) findViewById(R.id.layout_background); + layoutProgress = (LinearLayout) findViewById(R.id.layout_progress); + layoutSecondaryProgress = (LinearLayout) findViewById(R.id.layout_secondary_progress); + + initView(); + } + + // Retrieve initial parameter from view attribute + public void setupStyleable(Context context, AttributeSet attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundCornerProgress); + + radius = (int) typedArray.getDimension(R.styleable.RoundCornerProgress_rcRadius, dp2px(DEFAULT_PROGRESS_RADIUS)); + padding = (int) typedArray.getDimension(R.styleable.RoundCornerProgress_rcBackgroundPadding, dp2px(DEFAULT_BACKGROUND_PADDING)); + + isReverse = typedArray.getBoolean(R.styleable.RoundCornerProgress_rcReverse, false); + + max = typedArray.getFloat(R.styleable.RoundCornerProgress_rcMax, DEFAULT_MAX_PROGRESS); + progress = typedArray.getFloat(R.styleable.RoundCornerProgress_rcProgress, DEFAULT_PROGRESS); + secondaryProgress = typedArray.getFloat(R.styleable.RoundCornerProgress_rcSecondaryProgress, DEFAULT_SECONDARY_PROGRESS); + + int colorBackgroundDefault = context.getResources().getColor(R.color.round_corner_progress_bar_background_default); + colorBackground = typedArray.getColor(R.styleable.RoundCornerProgress_rcBackgroundColor, colorBackgroundDefault); + int colorProgressDefault = context.getResources().getColor(R.color.round_corner_progress_bar_progress_default); + colorProgress = typedArray.getColor(R.styleable.RoundCornerProgress_rcProgressColor, colorProgressDefault); + int colorSecondaryProgressDefault = context.getResources().getColor(R.color.round_corner_progress_bar_secondary_progress_default); + colorSecondaryProgress = typedArray.getColor(R.styleable.RoundCornerProgress_rcSecondaryProgressColor, colorSecondaryProgressDefault); + typedArray.recycle(); + + initStyleable(context, attrs); + } + + // Progress bar always refresh when view size has changed + @Override + protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) { + super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); + if (!isInEditMode()) { + totalWidth = newWidth; + drawAll(); + postDelayed(new Runnable() { + @Override + public void run() { + drawPrimaryProgress(); + drawSecondaryProgress(); + } + }, 5); + } + } + + // Redraw all view + protected void drawAll() { + drawBackgroundProgress(); + drawPadding(); + drawProgressReverse(); + drawPrimaryProgress(); + drawSecondaryProgress(); + onViewDraw(); + } + + // Draw progress background + @SuppressWarnings("deprecation") + private void drawBackgroundProgress() { + GradientDrawable backgroundDrawable = createGradientDrawable(colorBackground); + int newRadius = radius - (padding / 2); + backgroundDrawable.setCornerRadii(new float[]{newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius}); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + layoutBackground.setBackground(backgroundDrawable); + } else { + layoutBackground.setBackgroundDrawable(backgroundDrawable); + } + } + + // Create an empty color rectangle gradient drawable + protected GradientDrawable createGradientDrawable(int color) { + GradientDrawable gradientDrawable = new GradientDrawable(); + gradientDrawable.setShape(GradientDrawable.RECTANGLE); + gradientDrawable.setColor(color); + return gradientDrawable; + } + + private void drawPrimaryProgress() { + drawProgress(layoutProgress, max, progress, totalWidth, radius, padding, colorProgress, isReverse); + } + + private void drawSecondaryProgress() { + drawProgress(layoutSecondaryProgress, max, secondaryProgress, totalWidth, radius, padding, colorSecondaryProgress, isReverse); + } + + private void drawProgressReverse() { + setupReverse(layoutProgress); + setupReverse(layoutSecondaryProgress); + } + + // Change progress position by depending on isReverse flag + private void setupReverse(LinearLayout layoutProgress) { + RelativeLayout.LayoutParams progressParams = (RelativeLayout.LayoutParams) layoutProgress.getLayoutParams(); + removeLayoutParamsRule(progressParams); + if (isReverse) { + progressParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + // For support with RTL on API 17 or more + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) + progressParams.addRule(RelativeLayout.ALIGN_PARENT_END); + } else { + progressParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + // For support with RTL on API 17 or more + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) + progressParams.addRule(RelativeLayout.ALIGN_PARENT_START); + } + layoutProgress.setLayoutParams(progressParams); + } + + private void drawPadding() { + layoutBackground.setPadding(padding, padding, padding, padding); + } + + // Remove all of relative align rule + private void removeLayoutParamsRule(RelativeLayout.LayoutParams layoutParams) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); + layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_END); + layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_LEFT); + layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_START); + } else { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0); + } + } + + @SuppressLint("NewApi") + protected float dp2px(float dp) { + DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); + return Math.round(dp * (displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); + } + + public boolean isReverse() { + return isReverse; + } + + public void setReverse(boolean isReverse) { + this.isReverse = isReverse; + drawProgressReverse(); + drawPrimaryProgress(); + drawSecondaryProgress(); + } + + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + if (radius >= 0) + this.radius = radius; + drawBackgroundProgress(); + drawPrimaryProgress(); + drawSecondaryProgress(); + } + + public int getPadding() { + return padding; + } + + public void setPadding(int padding) { + if (padding >= 0) + this.padding = padding; + drawPadding(); + drawPrimaryProgress(); + drawSecondaryProgress(); + } + + public float getMax() { + return max; + } + + public void setMax(float max) { + if (max >= 0) + this.max = max; + if (this.progress > max) + this.progress = max; + drawPrimaryProgress(); + drawSecondaryProgress(); + } + + public float getLayoutWidth() { + return totalWidth; + } + + public float getProgress() { + return progress; + } + + public void setProgress(float progress) { + if (progress < 0) + this.progress = 0; + else if (progress > max) + this.progress = max; + else + this.progress = progress; + drawPrimaryProgress(); + if (progressChangedListener != null) + progressChangedListener.onProgressChanged(getId(), this.progress, true, false); + } + + public float getSecondaryProgressWidth() { + if (layoutSecondaryProgress != null) + return layoutSecondaryProgress.getWidth(); + return 0; + } + + public float getSecondaryProgress() { + return secondaryProgress; + } + + public void setSecondaryProgress(float secondaryProgress) { + if (secondaryProgress < 0) + this.secondaryProgress = 0; + else if (secondaryProgress > max) + this.secondaryProgress = max; + else + this.secondaryProgress = secondaryProgress; + drawSecondaryProgress(); + if (progressChangedListener != null) + progressChangedListener.onProgressChanged(getId(), this.secondaryProgress, false, true); + } + + public int getProgressBackgroundColor() { + return colorBackground; + } + + public void setProgressBackgroundColor(int colorBackground) { + this.colorBackground = colorBackground; + drawBackgroundProgress(); + } + + public int getProgressColor() { + return colorProgress; + } + + public void setProgressColor(int colorProgress) { + this.colorProgress = colorProgress; + drawPrimaryProgress(); + } + + public int getSecondaryProgressColor() { + return colorSecondaryProgress; + } + + public void setSecondaryProgressColor(int colorSecondaryProgress) { + this.colorSecondaryProgress = colorSecondaryProgress; + drawSecondaryProgress(); + } + + public void setOnProgressChangedListener(OnProgressChangedListener listener) { + progressChangedListener = listener; + } + + @Override + public void invalidate() { + super.invalidate(); + drawAll(); + } + + public interface OnProgressChangedListener { + void onProgressChanged(int viewId, float progress, boolean isPrimaryProgress, boolean isSecondaryProgress); + } +} + diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/IconRoundCornerProgressBar.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/IconRoundCornerProgressBar.java new file mode 100644 index 00000000..fcabb05b --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/progress/IconRoundCornerProgressBar.java @@ -0,0 +1,356 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.progress; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.kgurgul.cpuinfo.R; + +/** + * Fork from https://github.com/akexorcist/Android-RoundCornerProgressBar with saved instance state + * fix + */ +public class IconRoundCornerProgressBar extends BaseRoundCornerProgressBar implements View.OnClickListener { + + protected final static int DEFAULT_ICON_SIZE = 20; + protected final static int DEFAULT_ICON_PADDING_LEFT = 0; + protected final static int DEFAULT_ICON_PADDING_RIGHT = 0; + protected final static int DEFAULT_ICON_PADDING_TOP = 0; + protected final static int DEFAULT_ICON_PADDING_BOTTOM = 0; + + private ImageView ivProgressIcon; + private int iconResource; + private int iconSize; + private int iconWidth; + private int iconHeight; + private int iconPadding; + private int iconPaddingLeft; + private int iconPaddingRight; + private int iconPaddingTop; + private int iconPaddingBottom; + private int colorIconBackground; + + private OnIconClickListener iconClickListener; + + public IconRoundCornerProgressBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public IconRoundCornerProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public int initLayout() { + return R.layout.layout_icon_round_corner_progress_bar; + } + + @Override + protected void initStyleable(Context context, AttributeSet attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IconRoundCornerProgress); + + iconResource = typedArray.getResourceId(R.styleable.IconRoundCornerProgress_rcIconSrc, R.drawable.round_corner_progress_icon); + + iconSize = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconSize, -1); + iconWidth = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconWidth, dp2px(DEFAULT_ICON_SIZE)); + iconHeight = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconHeight, dp2px(DEFAULT_ICON_SIZE)); + iconPadding = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconPadding, -1); + iconPaddingLeft = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconPaddingLeft, dp2px(DEFAULT_ICON_PADDING_LEFT)); + iconPaddingRight = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconPaddingRight, dp2px(DEFAULT_ICON_PADDING_RIGHT)); + iconPaddingTop = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconPaddingTop, dp2px(DEFAULT_ICON_PADDING_TOP)); + iconPaddingBottom = (int) typedArray.getDimension(R.styleable.IconRoundCornerProgress_rcIconPaddingBottom, dp2px(DEFAULT_ICON_PADDING_BOTTOM)); + + int colorIconBackgroundDefault = context.getResources().getColor(R.color.round_corner_progress_bar_background_default); + colorIconBackground = typedArray.getColor(R.styleable.IconRoundCornerProgress_rcIconBackgroundColor, colorIconBackgroundDefault); + + typedArray.recycle(); + } + + @Override + protected void initView() { + ivProgressIcon = (ImageView) findViewById(R.id.iv_progress_icon); + ivProgressIcon.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.iv_progress_icon && iconClickListener != null) { + iconClickListener.onIconClick(); + } + } + + public void setOnIconClickListener(OnIconClickListener listener) { + iconClickListener = listener; + } + + @SuppressWarnings("deprecation") + @Override + protected void drawProgress(LinearLayout layoutProgress, float max, float progress, float totalWidth, + int radius, int padding, int colorProgress, boolean isReverse) { + GradientDrawable backgroundDrawable = createGradientDrawable(colorProgress); + int newRadius = radius - (padding / 2); + if (isReverse && progress != max) + backgroundDrawable.setCornerRadii(new float[]{newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius}); + else + backgroundDrawable.setCornerRadii(new float[]{0, 0, newRadius, newRadius, newRadius, newRadius, 0, 0}); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + layoutProgress.setBackground(backgroundDrawable); + } else { + layoutProgress.setBackgroundDrawable(backgroundDrawable); + } + + float ratio = max / progress; + int progressWidth = (int) ((totalWidth - ((padding * 2) + ivProgressIcon.getWidth())) / ratio); + ViewGroup.LayoutParams progressParams = layoutProgress.getLayoutParams(); + progressParams.width = progressWidth; + layoutProgress.setLayoutParams(progressParams); + } + + @Override + protected void onViewDraw() { + drawImageIcon(); + drawImageIconSize(); + drawImageIconPadding(); + drawIconBackgroundColor(); + } + + private void drawImageIcon() { + ivProgressIcon.setImageResource(iconResource); + } + + private void drawImageIconSize() { + if (iconSize == -1) + ivProgressIcon.setLayoutParams(new LayoutParams(iconWidth, iconHeight)); + else + ivProgressIcon.setLayoutParams(new LayoutParams(iconSize, iconSize)); + } + + private void drawImageIconPadding() { + if (iconPadding == -1 || iconPadding == 0) { + ivProgressIcon.setPadding(iconPaddingLeft, iconPaddingTop, iconPaddingRight, iconPaddingBottom); + } else { + ivProgressIcon.setPadding(iconPadding, iconPadding, iconPadding, iconPadding); + } + ivProgressIcon.invalidate(); + } + + @SuppressWarnings("deprecation") + private void drawIconBackgroundColor() { + GradientDrawable iconBackgroundDrawable = createGradientDrawable(colorIconBackground); + int radius = getRadius() - (getPadding() / 2); + iconBackgroundDrawable.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius}); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + ivProgressIcon.setBackground(iconBackgroundDrawable); + } else { + ivProgressIcon.setBackgroundDrawable(iconBackgroundDrawable); + } + } + + public int getIconImageResource() { + return iconResource; + } + + public void setIconImageResource(int resId) { + this.iconResource = resId; + drawImageIcon(); + } + + public int getIconSize() { + return iconSize; + } + + public void setIconSize(int size) { + if (size >= 0) + this.iconSize = size; + drawImageIconSize(); + } + + public int getIconPadding() { + return iconPadding; + } + + public void setIconPadding(int padding) { + if (padding >= 0) + this.iconPadding = padding; + drawImageIconPadding(); + } + + public int getIconPaddingLeft() { + return iconPaddingLeft; + } + + public void setIconPaddingLeft(int padding) { + if (padding > 0) + this.iconPaddingLeft = padding; + drawImageIconPadding(); + } + + public int getIconPaddingRight() { + return iconPaddingRight; + } + + public void setIconPaddingRight(int padding) { + if (padding > 0) + this.iconPaddingRight = padding; + drawImageIconPadding(); + } + + public int getIconPaddingTop() { + return iconPaddingTop; + } + + public void setIconPaddingTop(int padding) { + if (padding > 0) + this.iconPaddingTop = padding; + drawImageIconPadding(); + } + + public int getIconPaddingBottom() { + return iconPaddingBottom; + } + + public void setIconPaddingBottom(int padding) { + if (padding > 0) + this.iconPaddingBottom = padding; + drawImageIconPadding(); + } + + public int getColorIconBackground() { + return colorIconBackground; + } + + public void setIconBackgroundColor(int color) { + this.colorIconBackground = color; + drawIconBackgroundColor(); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + + ss.iconResource = this.iconResource; + ss.iconSize = this.iconSize; + ss.iconWidth = this.iconWidth; + ss.iconHeight = this.iconHeight; + + ss.iconPadding = this.iconPadding; + ss.iconPaddingLeft = this.iconPaddingLeft; + ss.iconPaddingRight = this.iconPaddingRight; + + ss.iconPaddingTop = this.iconPaddingTop; + ss.iconPaddingBottom = this.iconPaddingBottom; + ss.colorIconBackground = this.colorIconBackground; + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + this.iconResource = ss.iconResource; + this.iconSize = ss.iconSize; + this.iconWidth = ss.iconWidth; + this.iconHeight = ss.iconHeight; + this.iconPadding = ss.iconPadding; + this.iconPaddingLeft = ss.iconPaddingLeft; + this.iconPaddingRight = ss.iconPaddingRight; + this.iconPaddingTop = ss.iconPaddingTop; + this.iconPaddingBottom = ss.iconPaddingBottom; + this.colorIconBackground = ss.colorIconBackground; + } + + private static class SavedState extends BaseSavedState { + int iconResource; + int iconSize; + int iconWidth; + int iconHeight; + int iconPadding; + int iconPaddingLeft; + int iconPaddingRight; + int iconPaddingTop; + int iconPaddingBottom; + int colorIconBackground; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + + this.iconResource = in.readInt(); + this.iconSize = in.readInt(); + this.iconWidth = in.readInt(); + this.iconHeight = in.readInt(); + this.iconPadding = in.readInt(); + this.iconPaddingLeft = in.readInt(); + this.iconPaddingRight = in.readInt(); + this.iconPaddingTop = in.readInt(); + this.iconPaddingBottom = in.readInt(); + this.colorIconBackground = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + + out.writeInt(this.iconResource); + out.writeInt(this.iconSize); + out.writeInt(this.iconWidth); + out.writeInt(this.iconHeight); + out.writeInt(this.iconPadding); + out.writeInt(this.iconPaddingLeft); + out.writeInt(this.iconPaddingRight); + out.writeInt(this.iconPaddingTop); + out.writeInt(this.iconPaddingBottom); + out.writeInt(this.colorIconBackground); + } + + public static final Creator CREATOR = new Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + public interface OnIconClickListener { + void onIconClick(); + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RamUsageWidgetProvider.kt b/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RamUsageWidgetProvider.kt new file mode 100644 index 00000000..8d9d0a97 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RamUsageWidgetProvider.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.ramwidget + +import android.app.ActivityManager +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.widget.RemoteViews +import com.kgurgul.cpuinfo.R +import com.kgurgul.cpuinfo.features.HostActivity +import com.kgurgul.cpuinfo.utils.runOnApiBelow +import com.kgurgul.cpuinfo.widgets.arc.ArcProgress +import com.kgurgul.cpuinfo.widgets.ramwidget.events.KillRefreshServiceEvent +import org.greenrobot.eventbus.EventBus +import timber.log.Timber +import java.io.RandomAccessFile +import java.util.regex.Pattern + +/** + * Displays current usage of the RAM memory + * + * @author kgurgul + */ +class RamUsageWidgetProvider : AppWidgetProvider() { + + companion object { + var previousRamState = -1 + } + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + Timber.d("Update called") + + val thisWidget = ComponentName(context, RamUsageWidgetProvider::class.java) + val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget) + + // To ensure that RefreshService is running + if (!allWidgetIds.isEmpty()) { + val actualRamState = getUsedPercentageRam(context) + + if (actualRamState != previousRamState) { + Timber.d("Ram state changed from $previousRamState to $actualRamState") + previousRamState = actualRamState + + val remoteViews = RemoteViews(context.packageName, R.layout.widget) + remoteViews.setOnClickPendingIntent(R.id.arc_ram_status, buildButtonPendingIntent(context)) + remoteViews.setImageViewBitmap(R.id.arc_ram_status, getArcBitmap(context)) + + pushWidgetUpdates(context.applicationContext, remoteViews) + } + + // TODO: switch into foreground service + runOnApiBelow(Build.VERSION_CODES.O, { + val intent = Intent(context, RefreshService::class.java) + context.startService(intent) + }, {}) + } + + super.onUpdate(context, appWidgetManager, appWidgetIds) + } + + override fun onEnabled(context: Context) { + super.onEnabled(context) + Timber.d("Widget enabled") + previousRamState = -1 + } + + override fun onDisabled(context: Context) { + Timber.d("Widget disabled") + EventBus.getDefault().post(KillRefreshServiceEvent()) + previousRamState = -1 + + super.onDisabled(context) + } + + private fun buildButtonPendingIntent(context: Context): PendingIntent { + val intent = Intent(context, HostActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + return PendingIntent.getActivity(context, 0, intent, 0) + } + + private fun pushWidgetUpdates(context: Context, remoteViews: RemoteViews) { + val ramCleanerWidget = ComponentName(context, RamUsageWidgetProvider::class.java) + val manager = AppWidgetManager.getInstance(context) + manager.updateAppWidget(ramCleanerWidget, remoteViews) + } + + private fun getArcBitmap(context: Context): Bitmap { + val arcRamProgress = ArcProgress(context) + arcRamProgress.progress = previousRamState + arcRamProgress.bottomText = "RAM" + arcRamProgress.textSize = context.resources.getDimension(R.dimen.arc_text_size) + arcRamProgress.suffixTextSize = context.resources.getDimension(R.dimen.arc_suffix_text_size) + arcRamProgress.bottomTextSize = context.resources.getDimension(R.dimen.arc_bottom_text_size) + arcRamProgress.strokeWidth = context.resources.getDimension(R.dimen.arc_stroke_width) + arcRamProgress.measure(500, 500) + arcRamProgress.layout(0, 0, 500, 500) + val bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888) + arcRamProgress.draw(Canvas(bitmap)) + return bitmap + } + + @Synchronized + private fun getUsedPercentageRam(context: Context): Int { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val memoryInfo = ActivityManager.MemoryInfo() + activityManager.getMemoryInfo(memoryInfo) + val totalRam: Long + totalRam = if (Build.VERSION.SDK_INT >= 16) { + memoryInfo.totalMem + } else { + getTotalRamForOldApi() + } + return ((totalRam.toDouble() - memoryInfo.availMem.toDouble()) / totalRam.toDouble() + * 100.0).toInt() + } + + private fun getTotalRamForOldApi(): Long { + var reader: RandomAccessFile? = null + var totRam: Long = -1 + try { + reader = RandomAccessFile("/proc/meminfo", "r") + val load = reader.readLine() + + // Get the Number value from the string + val p = Pattern.compile("(\\d+)") + val m = p.matcher(load) + var value = "" + while (m.find()) { + value = m.group(1) + } + reader.close() + + totRam = value.toLong() + } catch (ex: Exception) { + Timber.e(ex) + } finally { + reader?.close() + } + + return totRam * 1024 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RefreshService.kt b/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RefreshService.kt new file mode 100644 index 00000000..f3c2aca9 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/RefreshService.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.ramwidget + +import android.app.Service +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.PowerManager +import android.preference.PreferenceManager +import com.kgurgul.cpuinfo.features.settings.SettingsFragment +import com.kgurgul.cpuinfo.widgets.ramwidget.events.KillRefreshServiceEvent +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import timber.log.Timber + +/** + * Really messy implementation to hack refreshing of the ram widget. TBH it should be refactored. + * With new Android O it should be migrated to the foreground service. + * + * @author kgurgul + */ +class RefreshService : Service() { + + companion object { + val RAM_BACKGROUND_DELAY = 60000L + var ramUpdateDelay = 10000L + } + + private var refreshHandler: Handler? = null + private lateinit var powerManager: PowerManager + private lateinit var prefs: SharedPreferences + + override fun onCreate() { + super.onCreate() + Timber.d("Service created") + + EventBus.getDefault().register(this) + + powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + prefs = PreferenceManager.getDefaultSharedPreferences(this) + ramUpdateDelay = prefs.getString(SettingsFragment.KEY_RAM_REFRESHING, "10000").toLong() + + refreshHandler = Handler() + refreshHandler!!.postDelayed(object : Runnable { + override fun run() { + ramUpdateDelay = + prefs.getString(SettingsFragment.KEY_RAM_REFRESHING, "10000").toLong() + + val isDeviceActive: Boolean = + if (Build.VERSION.SDK_INT >= 20) { + powerManager.isInteractive + } else { + @Suppress("DEPRECATION") + powerManager.isScreenOn + } + + Timber.d("Device is active: $isDeviceActive") + + if (isDeviceActive) { + Timber.d("Request for ram widget update - delay $ramUpdateDelay") + val intent = Intent(this@RefreshService, + RamUsageWidgetProvider::class.java) + intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + val widgetId = AppWidgetManager.getInstance(this@RefreshService) + .getAppWidgetIds(ComponentName(this@RefreshService, + RamUsageWidgetProvider::class.java)) + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetId) + this@RefreshService.sendBroadcast(intent) + + refreshHandler!!.postDelayed(this, ramUpdateDelay) + } else { + refreshHandler!!.postDelayed(this, RAM_BACKGROUND_DELAY) + } + } + }, ramUpdateDelay) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Timber.d("Add sticky parameter") + return START_STICKY + } + + override fun onDestroy() { + Timber.d("Service destroyed") + EventBus.getDefault().unregister(this) + refreshHandler?.removeCallbacksAndMessages(null) + super.onDestroy() + } + + @Subscribe + fun killServiceEvent(event: KillRefreshServiceEvent) { + stopSelf() + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/events/Events.kt b/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/events/Events.kt new file mode 100644 index 00000000..c21760eb --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/ramwidget/events/Events.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.ramwidget.events + +/** + * Created by kgurgul on 05/08/16. + */ +class KillRefreshServiceEvent \ No newline at end of file diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeHorizontalMenuLayout.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeHorizontalMenuLayout.java new file mode 100755 index 00000000..b80c89c9 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeHorizontalMenuLayout.java @@ -0,0 +1,352 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv; + +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewParent; + +import com.kgurgul.cpuinfo.R; +import com.kgurgul.cpuinfo.widgets.swiperv.listener.SwipeSwitchListener; +import com.kgurgul.cpuinfo.widgets.swiperv.swiper.LeftHorizontalSwiper; +import com.kgurgul.cpuinfo.widgets.swiperv.swiper.RightHorizontalSwiper; +import com.kgurgul.cpuinfo.widgets.swiperv.swiper.Swiper; + +public class SwipeHorizontalMenuLayout extends SwipeMenuLayout { + + protected int mPreScrollX; + protected float mPreLeftMenuFraction = -1; + protected float mPreRightMenuFraction = -1; + + public SwipeHorizontalMenuLayout(Context context) { + super(context); + } + + public SwipeHorizontalMenuLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SwipeHorizontalMenuLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + boolean isIntercepted = super.onInterceptTouchEvent(ev); + int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownX = mLastX = (int) ev.getX(); + mDownY = (int) ev.getY(); + isIntercepted = false; + break; + case MotionEvent.ACTION_MOVE: + int disX = (int) (ev.getX() - mDownX); + int disY = (int) (ev.getY() - mDownY); + isIntercepted = Math.abs(disX) > mScaledTouchSlop && Math.abs(disX) > Math.abs(disY); + break; + case MotionEvent.ACTION_UP: + isIntercepted = false; + // menu view opened and click on content view, + // we just close the menu view and intercept the up event + if (isMenuOpen() + && mCurrentSwiper.isClickOnContentView(this, ev.getX())) { + smoothCloseMenu(); + isIntercepted = true; + } + break; + case MotionEvent.ACTION_CANCEL: + isIntercepted = false; + if (!mScroller.isFinished()) + mScroller.forceFinished(false); + break; + } + return isIntercepted; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(ev); + int dx; + int dy; + int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mLastX = (int) ev.getX(); + mLastY = (int) ev.getY(); + break; + case MotionEvent.ACTION_MOVE: + if (!isSwipeEnable()) break; + int disX = (int) (mLastX - ev.getX()); + int disY = (int) (mLastY - ev.getY()); + if (!mDragging + && Math.abs(disX) > mScaledTouchSlop + && Math.abs(disX) > Math.abs(disY)) { + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + mDragging = true; + } + if (mDragging) { + if (mCurrentSwiper == null || shouldResetSwiper) { + if (disX < 0) { + if (mBeginSwiper != null) + mCurrentSwiper = mBeginSwiper; + else + mCurrentSwiper = mEndSwiper; + } else { + if (mEndSwiper != null) + mCurrentSwiper = mEndSwiper; + else + mCurrentSwiper = mBeginSwiper; + } + } + scrollBy(disX, 0); + mLastX = (int) ev.getX(); + mLastY = (int) ev.getY(); + shouldResetSwiper = false; + } + break; + case MotionEvent.ACTION_UP: + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(false); + } + dx = (int) (mDownX - ev.getX()); + dy = (int) (mDownY - ev.getY()); + mDragging = false; + mVelocityTracker.computeCurrentVelocity(1000, mScaledMaximumFlingVelocity); + int velocityX = (int) mVelocityTracker.getXVelocity(); + int velocity = Math.abs(velocityX); + if (velocity > mScaledMinimumFlingVelocity) { + if (mCurrentSwiper != null) { + int duration = getSwipeDuration(ev, velocity); + if (mCurrentSwiper instanceof RightHorizontalSwiper) { + if (velocityX < 0) { // just open + smoothOpenMenu(duration); + } else { // just close + smoothCloseMenu(duration); + } + } else { + if (velocityX > 0) { // just open + smoothOpenMenu(duration); + } else { // just close + smoothCloseMenu(duration); + } + } + ViewCompat.postInvalidateOnAnimation(this); + } + } else { + judgeOpenClose(dx, dy); + } + mVelocityTracker.clear(); + mVelocityTracker.recycle(); + mVelocityTracker = null; + if (Math.abs(dx) > mScaledTouchSlop + || Math.abs(dy) > mScaledTouchSlop + || isMenuOpen()) { // ignore click listener, cancel this event + MotionEvent motionEvent = MotionEvent.obtain(ev); + motionEvent.setAction(MotionEvent.ACTION_CANCEL); + return super.onTouchEvent(motionEvent); + } + break; + case MotionEvent.ACTION_CANCEL: + mDragging = false; + if (!mScroller.isFinished()) { + mScroller.forceFinished(false); + } else { + dx = (int) (mDownX - ev.getX()); + dy = (int) (mDownY - ev.getY()); + judgeOpenClose(dx, dy); + } + break; + } + return super.onTouchEvent(ev); + } + + private void judgeOpenClose(int dx, int dy) { + if (mCurrentSwiper != null) { + if (Math.abs(getScrollX()) >= (mCurrentSwiper.getMenuView().getWidth() * mAutoOpenPercent)) { // auto open + if (Math.abs(dx) > mScaledTouchSlop || Math.abs(dy) > mScaledTouchSlop) { // swipe up + if (isMenuOpenNotEqual()) smoothCloseMenu(); + else smoothOpenMenu(); + } else { // normal up + if (isMenuOpen()) smoothCloseMenu(); + else smoothOpenMenu(); + } + } else { // auto close + smoothCloseMenu(); + } + } + } + + @Override + public void scrollTo(int x, int y) { + Swiper.Checker checker = mCurrentSwiper.checkXY(x, y); + shouldResetSwiper = checker.shouldResetSwiper; + if (checker.x != getScrollX()) { + super.scrollTo(checker.x, checker.y); + } + if (getScrollX() != mPreScrollX) { + int absScrollX = Math.abs(getScrollX()); + if (mCurrentSwiper instanceof LeftHorizontalSwiper) { + if (mSwipeSwitchListener != null) { + if (absScrollX == 0) mSwipeSwitchListener.beginMenuClosed(this); + else if (absScrollX == mBeginSwiper.getMenuWidth()) + mSwipeSwitchListener.beginMenuOpened(this); + } + if (mSwipeFractionListener != null) { + float fraction = (float) absScrollX / mBeginSwiper.getMenuWidth(); + fraction = Float.parseFloat(mDecimalFormat.format(fraction)); + if (fraction != mPreLeftMenuFraction) { + mSwipeFractionListener.beginMenuSwipeFraction(this, fraction); + } + mPreLeftMenuFraction = fraction; + } + } else { + if (mSwipeSwitchListener != null) { + if (absScrollX == 0) mSwipeSwitchListener.endMenuClosed(this); + else if (absScrollX == mEndSwiper.getMenuWidth()) + mSwipeSwitchListener.endMenuOpened(this); + } + if (mSwipeFractionListener != null) { + float fraction = (float) absScrollX / mEndSwiper.getMenuWidth(); + fraction = Float.parseFloat(mDecimalFormat.format(fraction)); + if (fraction != mPreRightMenuFraction) { + mSwipeFractionListener.endMenuSwipeFraction(this, fraction); + } + mPreRightMenuFraction = fraction; + } + } + } + mPreScrollX = getScrollX(); + } + + @Override + public void computeScroll() { + if (mScroller.computeScrollOffset()) { + int currX = Math.abs(mScroller.getCurrX()); + if (mCurrentSwiper instanceof RightHorizontalSwiper) { + scrollTo(currX, 0); + invalidate(); + } else { + scrollTo(-currX, 0); + invalidate(); + } + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + setClickable(true); + mContentView = findViewById(R.id.smContentView); + if (mContentView == null) { + throw new IllegalArgumentException("Not find contentView by id smContentView"); + } + View menuViewLeft = findViewById(R.id.smMenuViewLeft); + View menuViewRight = findViewById(R.id.smMenuViewRight); + if (menuViewLeft == null && menuViewRight == null) { + throw new IllegalArgumentException("Not find menuView by id (smMenuViewLeft, smMenuViewRight)"); + } + if (menuViewLeft != null) mBeginSwiper = new LeftHorizontalSwiper(menuViewLeft); + if (menuViewRight != null) mEndSwiper = new RightHorizontalSwiper(menuViewRight); + } + + public boolean isMenuOpen() { + return (mBeginSwiper != null && mBeginSwiper.isMenuOpen(getScrollX())) + || (mEndSwiper != null && mEndSwiper.isMenuOpen(getScrollX())); + } + + public boolean isMenuOpenNotEqual() { + return (mBeginSwiper != null && mBeginSwiper.isMenuOpenNotEqual(getScrollX())) + || (mEndSwiper != null && mEndSwiper.isMenuOpenNotEqual(getScrollX())); + } + + public void smoothOpenMenu(int duration) { + mCurrentSwiper.autoOpenMenu(mScroller, getScrollX(), duration); + invalidate(); + } + + public void smoothCloseMenu(int duration) { + mCurrentSwiper.autoCloseMenu(mScroller, getScrollX(), duration); + invalidate(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + + int parentViewWidth = ViewCompat.getMeasuredWidthAndState(this); + int contentViewWidth = ViewCompat.getMeasuredWidthAndState(mContentView); + int contentViewHeight = ViewCompat.getMeasuredHeightAndState(mContentView); + LayoutParams lp = (LayoutParams) mContentView.getLayoutParams(); + int lGap = getPaddingLeft() + lp.leftMargin; + int tGap = getPaddingTop() + lp.topMargin; + mContentView.layout(lGap, + tGap, + lGap + contentViewWidth, + tGap + contentViewHeight); + if (mEndSwiper != null) { + int menuViewWidth = ViewCompat.getMeasuredWidthAndState(mEndSwiper.getMenuView()); + int menuViewHeight = ViewCompat.getMeasuredHeightAndState(mEndSwiper.getMenuView()); + lp = (LayoutParams) mEndSwiper.getMenuView().getLayoutParams(); + tGap = getPaddingTop() + lp.topMargin; + mEndSwiper.getMenuView().layout(parentViewWidth, + tGap, + parentViewWidth + menuViewWidth, + tGap + menuViewHeight); + } + if (mBeginSwiper != null) { + int menuViewWidth = ViewCompat.getMeasuredWidthAndState(mBeginSwiper.getMenuView()); + int menuViewHeight = ViewCompat.getMeasuredHeightAndState(mBeginSwiper.getMenuView()); + lp = (LayoutParams) mBeginSwiper.getMenuView().getLayoutParams(); + tGap = getPaddingTop() + lp.topMargin; + mBeginSwiper.getMenuView().layout(-menuViewWidth, + tGap, + 0, + tGap + menuViewHeight); + } + } + + public void setSwipeEnable(boolean swipeEnable) { + this.swipeEnable = swipeEnable; + } + + public boolean isSwipeEnable() { + return swipeEnable; + } + + public void setSwipeListener(SwipeSwitchListener swipeSwitchListener) { + mSwipeSwitchListener = swipeSwitchListener; + } + + int getLen() { + return mCurrentSwiper.getMenuWidth(); + } + + int getMoveLen(MotionEvent ev) { + int sx = getScrollX(); + return (int) (ev.getX() - sx); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuHelper.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuHelper.java new file mode 100755 index 00000000..9537216a --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuHelper.java @@ -0,0 +1,142 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv; + +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by tubingbing on 16/8/11. + */ + +public class SwipeMenuHelper { + + public static final int INVALID_POSITION = -1; + + protected Callback mCallback; + protected ViewConfiguration mViewConfig; + protected SwipeHorizontalMenuLayout mOldSwipedView; + protected int mOldTouchedPosition = INVALID_POSITION; + + public SwipeMenuHelper(Context context, Callback callback) { + mCallback = callback; + mViewConfig = ViewConfiguration.get(context); + } + + /** + * Handle touch down event, decide whether intercept or not. + * + * @param ev Touch event + * @param defaultIntercepted The default intercept status + * @return Whether intercept or not + */ + public boolean handleListDownTouchEvent(MotionEvent ev, boolean defaultIntercepted) { + boolean isIntercepted = defaultIntercepted; + View touchingView = findChildViewUnder((int) ev.getX(), (int) ev.getY()); + int touchingPosition; + if (touchingView != null) { + touchingPosition = mCallback.getPositionForView(touchingView); + } else { + touchingPosition = INVALID_POSITION; + } + if (touchingPosition != mOldTouchedPosition && mOldSwipedView != null) { + // already one swipe menu is opened, so we close it and intercept the event + if (mOldSwipedView.isMenuOpen()) { + mOldSwipedView.smoothCloseMenu(); + isIntercepted = true; + } + } + touchingView = mCallback.transformTouchingView(touchingPosition, touchingView); + if (touchingView != null) { + View itemView = getSwipeMenuView((ViewGroup) touchingView); + if (itemView != null && itemView instanceof SwipeHorizontalMenuLayout) { + mOldSwipedView = (SwipeHorizontalMenuLayout) itemView; + mOldTouchedPosition = touchingPosition; + } + } + // if we intercept the event, just reset + if (isIntercepted) { + mOldSwipedView = null; + mOldTouchedPosition = INVALID_POSITION; + } + return isIntercepted; + } + + public View getSwipeMenuView(ViewGroup itemView) { + if (itemView instanceof SwipeHorizontalMenuLayout) { + return itemView; + } + List unvisited = new ArrayList<>(); + unvisited.add(itemView); + while (!unvisited.isEmpty()) { + View child = unvisited.remove(0); + if (!(child instanceof ViewGroup)) { // view + continue; + } + if (child instanceof SwipeHorizontalMenuLayout) { + return child; + } + ViewGroup group = (ViewGroup) child; + final int childCount = group.getChildCount(); + for (int i = 0; i < childCount; i++) { + unvisited.add(group.getChildAt(i)); + } + } + return itemView; + } + + /** + * Find the topmost view under the given point. + * + * @param x Horizontal position in pixels to search + * @param y Vertical position in pixels to search + * @return The child view under (x, y) or null if no matching child is found + */ + public View findChildViewUnder(float x, float y) { + final int count = mCallback.getRealChildCount(); + for (int i = count - 1; i >= 0; i--) { + final View child = mCallback.getRealChildAt(i); + final float translationX = ViewCompat.getTranslationX(child); + final float translationY = ViewCompat.getTranslationY(child); + if (x >= child.getLeft() + translationX && + x <= child.getRight() + translationX && + y >= child.getTop() + translationY && + y <= child.getBottom() + translationY) { + return child; + } + } + return null; + } + + public interface Callback { + int getPositionForView(View view); + + int getRealChildCount(); + + View getRealChildAt(int index); + + View transformTouchingView(int touchingPosition, View touchingView); + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuLayout.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuLayout.java new file mode 100755 index 00000000..a0904e94 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuLayout.java @@ -0,0 +1,186 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; +import android.widget.OverScroller; + +import com.kgurgul.cpuinfo.R; +import com.kgurgul.cpuinfo.widgets.swiperv.listener.SwipeFractionListener; +import com.kgurgul.cpuinfo.widgets.swiperv.listener.SwipeSwitchListener; +import com.kgurgul.cpuinfo.widgets.swiperv.swiper.Swiper; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; + +public abstract class SwipeMenuLayout extends FrameLayout { + + public static final String TAG = "sml"; + public static final int DEFAULT_SCROLLER_DURATION = 250; + public static final float DEFAULT_AUTO_OPEN_PERCENT = 0.5f; + protected float mAutoOpenPercent = DEFAULT_AUTO_OPEN_PERCENT; + protected int mScrollerDuration = DEFAULT_SCROLLER_DURATION; + + protected int mScaledTouchSlop; + protected int mLastX; + protected int mLastY; + protected int mDownX; + protected int mDownY; + protected View mContentView; + protected Swiper mBeginSwiper; + protected Swiper mEndSwiper; + protected Swiper mCurrentSwiper; + protected boolean shouldResetSwiper; + protected boolean mDragging; + protected boolean swipeEnable = true; + protected OverScroller mScroller; + protected Interpolator mInterpolator; + protected VelocityTracker mVelocityTracker; + protected int mScaledMinimumFlingVelocity; + protected int mScaledMaximumFlingVelocity; + protected SwipeSwitchListener mSwipeSwitchListener; + protected SwipeFractionListener mSwipeFractionListener; + protected NumberFormat mDecimalFormat = new DecimalFormat("#.00", new DecimalFormatSymbols(Locale.US)); + + public SwipeMenuLayout(Context context) { + this(context, null); + } + + public SwipeMenuLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + if (!isInEditMode()) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeMenu, 0, defStyle); + int interpolatorId = a.getResourceId(R.styleable.SwipeMenu_sml_scroller_interpolator, -1); + if (interpolatorId > 0) + mInterpolator = AnimationUtils.loadInterpolator(getContext(), interpolatorId); + mAutoOpenPercent = a.getFloat(R.styleable.SwipeMenu_sml_auto_open_percent, DEFAULT_AUTO_OPEN_PERCENT); + mScrollerDuration = a.getInteger(R.styleable.SwipeMenu_sml_scroller_duration, DEFAULT_SCROLLER_DURATION); + a.recycle(); + } + init(); + } + + public void smoothOpenBeginMenu() { + if (mBeginSwiper == null) throw new IllegalArgumentException("Not have begin menu!"); + mCurrentSwiper = mBeginSwiper; + smoothOpenMenu(); + } + + public void smoothOpenEndMenu() { + if (mEndSwiper == null) throw new IllegalArgumentException("Not have end menu!"); + mCurrentSwiper = mEndSwiper; + smoothOpenMenu(); + } + + public void smoothCloseBeginMenu() { + if (mBeginSwiper == null) throw new IllegalArgumentException("Not have begin menu!"); + mCurrentSwiper = mBeginSwiper; + smoothCloseMenu(); + } + + public void smoothCloseEndMenu() { + if (mEndSwiper == null) throw new IllegalArgumentException("Not have end menu!"); + mCurrentSwiper = mEndSwiper; + smoothCloseMenu(); + } + + public abstract void smoothOpenMenu(int duration); + + public void smoothOpenMenu() { + smoothOpenMenu(mScrollerDuration); + } + + public abstract void smoothCloseMenu(int duration); + + public void smoothCloseMenu() { + smoothCloseMenu(mScrollerDuration); + } + + public void init() { + ViewConfiguration mViewConfig = ViewConfiguration.get(getContext()); + mScaledTouchSlop = mViewConfig.getScaledTouchSlop(); + mScroller = new OverScroller(getContext(), mInterpolator); + mScaledMinimumFlingVelocity = mViewConfig.getScaledMinimumFlingVelocity(); + mScaledMaximumFlingVelocity = mViewConfig.getScaledMaximumFlingVelocity(); + } + + public void setSwipeEnable(boolean swipeEnable) { + this.swipeEnable = swipeEnable; + } + + public boolean isSwipeEnable() { + return swipeEnable; + } + + public void setSwipeListener(SwipeSwitchListener swipeSwitchListener) { + mSwipeSwitchListener = swipeSwitchListener; + } + + public void setSwipeFractionListener(SwipeFractionListener swipeFractionListener) { + mSwipeFractionListener = swipeFractionListener; + } + + abstract int getMoveLen(MotionEvent event); + + abstract int getLen(); + + /** + * compute finish duration + * + * @param ev up event + * @param velocity velocity + * @return finish duration + */ + int getSwipeDuration(MotionEvent ev, int velocity) { + int moveLen = getMoveLen(ev); + final int len = getLen(); + final int halfLen = len / 2; + final float distanceRatio = Math.min(1f, 1.0f * Math.abs(moveLen) / len); + final float distance = halfLen + halfLen * + distanceInfluenceForSnapDuration(distanceRatio); + int duration; + if (velocity > 0) { + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + } else { + final float pageDelta = (float) Math.abs(moveLen) / len; + duration = (int) ((pageDelta + 1) * 100); + } + duration = Math.min(duration, mScrollerDuration); + return duration; + } + + float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuRecyclerView.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuRecyclerView.java new file mode 100755 index 00000000..cfd3b36a --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/SwipeMenuRecyclerView.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class SwipeMenuRecyclerView extends RecyclerView implements SwipeMenuHelper.Callback { + + protected SwipeMenuHelper mHelper; + + public SwipeMenuRecyclerView(Context context) { + super(context); + init(); + } + + public SwipeMenuRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SwipeMenuRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + protected void init() { + mHelper = new SwipeMenuHelper(getContext(), this); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + boolean isIntercepted = super.onInterceptTouchEvent(ev); + // ignore Multi-Touch + if (ev.getActionIndex() != 0) return true; + int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + isIntercepted = mHelper.handleListDownTouchEvent(ev, isIntercepted); + break; + } + return isIntercepted; + } + + public int getPositionForView(View touchView) { + return getChildAdapterPosition(touchView); + } + + @Override + public int getRealChildCount() { + return getChildCount(); + } + + @Override + public View getRealChildAt(int index) { + return getChildAt(index); + } + + @Override + public View transformTouchingView(int touchingPosition, View touchingView) { + ViewHolder vh = findViewHolderForAdapterPosition(touchingPosition); + if (vh != null) { + return vh.itemView; + } + return touchingView; + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeFractionListener.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeFractionListener.java new file mode 100755 index 00000000..6c864af2 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeFractionListener.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.listener; + +import com.kgurgul.cpuinfo.widgets.swiperv.SwipeMenuLayout; + +/** + * Created by tubingbing on 16/5/20. + */ +public class SimpleSwipeFractionListener implements SwipeFractionListener { + @Override + public void beginMenuSwipeFraction(SwipeMenuLayout swipeMenuLayout, float fraction) { + + } + + @Override + public void endMenuSwipeFraction(SwipeMenuLayout swipeMenuLayout, float fraction) { + + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeSwitchListener.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeSwitchListener.java new file mode 100755 index 00000000..09cb4b22 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SimpleSwipeSwitchListener.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.listener; + +import com.kgurgul.cpuinfo.widgets.swiperv.SwipeMenuLayout; + +/** + * Created by tubingbing on 16/5/18. + */ +public class SimpleSwipeSwitchListener implements SwipeSwitchListener { + + @Override + public void beginMenuClosed(SwipeMenuLayout swipeMenuLayout) { + + } + + @Override + public void beginMenuOpened(SwipeMenuLayout swipeMenuLayout) { + + } + + @Override + public void endMenuClosed(SwipeMenuLayout swipeMenuLayout) { + + } + + @Override + public void endMenuOpened(SwipeMenuLayout swipeMenuLayout) { + + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeFractionListener.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeFractionListener.java new file mode 100755 index 00000000..68151a47 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeFractionListener.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.listener; + +import com.kgurgul.cpuinfo.widgets.swiperv.SwipeMenuLayout; + +/** + * Created by tubingbing on 16/5/20. + */ +public interface SwipeFractionListener { + void beginMenuSwipeFraction(SwipeMenuLayout swipeMenuLayout, float fraction); + + void endMenuSwipeFraction(SwipeMenuLayout swipeMenuLayout, float fraction); +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeSwitchListener.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeSwitchListener.java new file mode 100755 index 00000000..bf5e466c --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/listener/SwipeSwitchListener.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.listener; + +import com.kgurgul.cpuinfo.widgets.swiperv.SwipeMenuLayout; + +/** + * Created by tubingbing on 16/5/18. + */ +public interface SwipeSwitchListener { + + void beginMenuClosed(SwipeMenuLayout swipeMenuLayout); + + void beginMenuOpened(SwipeMenuLayout swipeMenuLayout); + + void endMenuClosed(SwipeMenuLayout swipeMenuLayout); + + void endMenuOpened(SwipeMenuLayout swipeMenuLayout); + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/BottomVerticalSwiper.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/BottomVerticalSwiper.java new file mode 100755 index 00000000..dec5a89d --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/BottomVerticalSwiper.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.swiper; + +import android.view.View; +import android.widget.OverScroller; + +/** + * Created by tubingbing on 16/4/11. + */ +public class BottomVerticalSwiper extends Swiper { + + public BottomVerticalSwiper(View menuView) { + super(END_DIRECTION, menuView); + } + + @Override + public boolean isMenuOpen(int scrollY) { + return scrollY >= -getMenuView().getHeight() * getDirection(); + } + + @Override + public boolean isMenuOpenNotEqual(int scrollY) { + return scrollY > -getMenuView().getHeight() * getDirection(); + } + + @Override + public void autoOpenMenu(OverScroller scroller, int scrollY, int duration) { + scroller.startScroll(0, Math.abs(scrollY), 0, getMenuView().getHeight() - Math.abs(scrollY), duration); + } + + @Override + public void autoCloseMenu(OverScroller scroller, int scrollY, int duration) { + scroller.startScroll(0, -Math.abs(scrollY), 0, Math.abs(scrollY), duration); + } + + @Override + public Checker checkXY(int x, int y) { + mChecker.x = x; + mChecker.y = y; + mChecker.shouldResetSwiper = false; + if (mChecker.y == 0) { + mChecker.shouldResetSwiper = true; + } + if (mChecker.y < 0) { + mChecker.y = 0; + } + if (mChecker.y > getMenuView().getHeight()) { + mChecker.y = getMenuView().getHeight(); + } + return mChecker; + } + + @Override + public boolean isClickOnContentView(View contentView, float y) { + return y < (contentView.getHeight() - getMenuView().getHeight()); + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/LeftHorizontalSwiper.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/LeftHorizontalSwiper.java new file mode 100755 index 00000000..5bf112d6 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/LeftHorizontalSwiper.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.swiper; + +import android.view.View; +import android.widget.OverScroller; + +/** + * Created by tubingbing on 16/4/11. + */ +public class LeftHorizontalSwiper extends Swiper { + + public LeftHorizontalSwiper(View menuView) { + super(BEGIN_DIRECTION, menuView); + } + + @Override + public boolean isMenuOpen(int scrollX) { + return scrollX <= -getMenuView().getWidth() * getDirection(); + } + + @Override + public boolean isMenuOpenNotEqual(int scrollX) { + return scrollX < -getMenuView().getWidth() * getDirection(); + } + + @Override + public void autoOpenMenu(OverScroller scroller, int scrollX, int duration) { + scroller.startScroll(Math.abs(scrollX), 0, getMenuView().getWidth() - Math.abs(scrollX), 0, duration); + } + + @Override + public void autoCloseMenu(OverScroller scroller, int scrollX, int duration) { + scroller.startScroll(-Math.abs(scrollX), 0, Math.abs(scrollX), 0, duration); + } + + @Override + public Checker checkXY(int x, int y) { + mChecker.x = x; + mChecker.y = y; + mChecker.shouldResetSwiper = false; + if (mChecker.x == 0) { + mChecker.shouldResetSwiper = true; + } + if (mChecker.x >= 0) { + mChecker.x = 0; + } + if (mChecker.x <= -getMenuView().getWidth()) { + mChecker.x = -getMenuView().getWidth(); + } + return mChecker; + } + + @Override + public boolean isClickOnContentView(View contentView, float x) { + return x > getMenuView().getWidth(); + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/RightHorizontalSwiper.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/RightHorizontalSwiper.java new file mode 100755 index 00000000..a6982339 --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/RightHorizontalSwiper.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.swiper; + +import android.view.View; +import android.widget.OverScroller; + +/** + * Created by tubingbing on 16/4/11. + */ +public class RightHorizontalSwiper extends Swiper { + + public RightHorizontalSwiper(View menuView) { + super(END_DIRECTION, menuView); + } + + @Override + public boolean isMenuOpen(int scrollX) { + return scrollX >= -getMenuView().getWidth() * getDirection(); + } + + @Override + public boolean isMenuOpenNotEqual(int scrollX) { + return scrollX > -getMenuView().getWidth() * getDirection(); + } + + @Override + public void autoOpenMenu(OverScroller scroller, int scrollX, int duration) { + scroller.startScroll(Math.abs(scrollX), 0, getMenuView().getWidth() - Math.abs(scrollX), 0, duration); + } + + @Override + public void autoCloseMenu(OverScroller scroller, int scrollX, int duration) { + scroller.startScroll(-Math.abs(scrollX), 0, Math.abs(scrollX), 0, duration); + } + + @Override + public Checker checkXY(int x, int y) { + mChecker.x = x; + mChecker.y = y; + mChecker.shouldResetSwiper = false; + if (mChecker.x == 0) { + mChecker.shouldResetSwiper = true; + } + if (mChecker.x < 0) { + mChecker.x = 0; + } + if (mChecker.x > getMenuView().getWidth()) { + mChecker.x = getMenuView().getWidth(); + } + return mChecker; + } + + @Override + public boolean isClickOnContentView(View contentView, float x) { + return x < (contentView.getWidth() - getMenuView().getWidth()); + } +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/Swiper.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/Swiper.java new file mode 100755 index 00000000..d6983d0c --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/Swiper.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.swiper; + +import android.view.View; +import android.widget.OverScroller; + +/** + * Created by tubingbing on 16/4/11. + */ +public abstract class Swiper { + + protected static final int BEGIN_DIRECTION = 1; + protected static final int END_DIRECTION = -1; + + private int direction; + private View menuView; + protected Checker mChecker; + + public Swiper(int direction, View menuView) { + this.direction = direction; + this.menuView = menuView; + mChecker = new Checker(); + } + + public abstract boolean isMenuOpen(final int scrollDis); + + public abstract boolean isMenuOpenNotEqual(final int scrollDis); + + public abstract void autoOpenMenu(OverScroller scroller, int scrollDis, int duration); + + public abstract void autoCloseMenu(OverScroller scroller, int scrollDis, int duration); + + public abstract Checker checkXY(int x, int y); + + public abstract boolean isClickOnContentView(View contentView, float clickPoint); + + public int getDirection() { + return direction; + } + + public View getMenuView() { + return menuView; + } + + public int getMenuWidth() { + return getMenuView().getWidth(); + } + + public int getMenuHeight() { + return getMenuView().getHeight(); + } + + public static final class Checker { + public int x; + public int y; + public boolean shouldResetSwiper; + } + +} diff --git a/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/TopVerticalSwiper.java b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/TopVerticalSwiper.java new file mode 100755 index 00000000..9706e6af --- /dev/null +++ b/app/src/main/java/com/kgurgul/cpuinfo/widgets/swiperv/swiper/TopVerticalSwiper.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 KG Soft + * + * 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.kgurgul.cpuinfo.widgets.swiperv.swiper; + +import android.view.View; +import android.widget.OverScroller; + +/** + * Created by tubingbing on 16/4/11. + */ +public class TopVerticalSwiper extends Swiper { + + public TopVerticalSwiper(View menuView) { + super(BEGIN_DIRECTION, menuView); + } + + @Override + public boolean isMenuOpen(int scrollY) { + return scrollY <= -getMenuView().getHeight() * getDirection(); + } + + @Override + public boolean isMenuOpenNotEqual(int scrollY) { + return scrollY < -getMenuView().getHeight() * getDirection(); + } + + @Override + public void autoOpenMenu(OverScroller scroller, int scrollY, int duration) { + scroller.startScroll(0, Math.abs(scrollY), 0, getMenuView().getHeight() - Math.abs(scrollY), duration); + } + + @Override + public void autoCloseMenu(OverScroller scroller, int scrollY, int duration) { + scroller.startScroll(0, -Math.abs(scrollY), 0, Math.abs(scrollY), duration); + } + + @Override + public Checker checkXY(int x, int y) { + mChecker.x = x; + mChecker.y = y; + mChecker.shouldResetSwiper = false; + if (mChecker.y == 0) { + mChecker.shouldResetSwiper = true; + } + if (mChecker.y >= 0) { + mChecker.y = 0; + } + if (mChecker.y <= -getMenuView().getHeight()) { + mChecker.y = -getMenuView().getHeight(); + } + return mChecker; + } + + @Override + public boolean isClickOnContentView(View contentView, float y) { + return y > getMenuView().getHeight(); + } +} diff --git a/app/src/main/res/drawable-hdpi/nav_drawer_header.png b/app/src/main/res/drawable-hdpi/nav_drawer_header.png new file mode 100644 index 0000000000000000000000000000000000000000..d43cb0113922fadb4552bbae79fd00bf288ccc62 GIT binary patch literal 36293 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 cX6Sbmqn2xix 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 IZRi^DfA|@U{{R30 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/round_corner_progress_icon.png b/app/src/main/res/drawable-mdpi/round_corner_progress_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..991e5e05792ed25fa9a3c64abb88b23c6b348b63 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtbDl1aAr-gY&N#^1Y{281$Sk2z zzC>q=tCkFXOvtsh98Y`^ElZD{dF{V%uI1p7_BlM{j3fS_G*(L~dYHrktnw7VzS zeBP9{C;Ef2_6Dum<)?1_)H`S$mbiAy{LpVZRyW5-=>M9Z`@ZPubbFyc%Q?$k&xu?0 zFA5XBlzXV(a{qF*Ca>~IW)UxIww3!yvDs`roGsD8%j?3+ymDdVl0*e_lL?6rI5kuD zb)4NE_}Sj*_TrYGiR-!xcXc!Sio2iEYoDzB}-f*N`mv#O3D+9QW?t2%k?tzvWt@w3sUv+i_&Mmvyoz8V7wmS z6XN>+|9_zRQ83IxK>gRloj@z_Pj#_YKZ7A{)6Wci9!tJkbuw_)R^&0Du^-?4k|z5@pjA31jN)ammVE?vHM z4a4OJMCwt{t`Pc1pw`k{X z%C~=Wjy-)}sg-Bld()e0mHCWJ6K5!NMW`?c%n)fYlx*lw=;b()!DuLO(m|)*l`}Iq%yo8XFTHiO)tY_rTi?~X`%k(@&WK7-o2QX(v`wOn zr6P2S#<;|VOGeZqki=AieSoG?zrq~xN#wF%y$JYN$`J0q7duHyLlzYjs5m$dR zh?Q6}-kEk-e~(>#-S@Rj4QGYlG4*h9R?LaE72o^qjM3v5#ygW9%Wtgj*uiw8G54B; zNke2zV5+GabK=S|wTv39OxXhclk{O$AR2|9ILS<2=Ul627MPfWF4 zCi{(kU!g3A&Cd+?D(Shbatq#YZv*3%*`?iA9+zkS*NAG_u}U{%Gb78{O$($70&dr* zmdQTt&yqe671NT^z_xi!&l`1%hSL2j)Doq|%7S$dYnWv&suGZ_+w6HUTB#x9h*{`n zmj>&iT{3D+D$~6k>+`l=yjgd@KvLn|s_iMZ-i!(skBk)ddT!K|U1z#EI)<&o{OHeZ zPcJX8G5US;Ztl+1XbpZf{&R0*A}5^PTk4@js%Xg+1{LV-#w%GgN-@}k<8~fJ> zKgJY027YY%(zWczh4NG0t1c~$iwk-hbNTJte;@zHhJ1=O#7G>_B=dl?;m^_ZPvQGd f#@8*c`}<#iv&rmgzX$5lpu)q`)z4*}Q$iB})bhM; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/folder.png b/app/src/main/res/drawable-xhdpi/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..71a5a137c463dfd97dfad592c86b7eac773664c5 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D8c!F;kch)?uX*!1I0(1~u3k7j z(@|)CtD?lCSzab=p|%h2vhJCDWr_9aDM8CMGG^>pz2)3<9-DOK6&2^5E;t7@iOv_Z za^ku1A-hLl9mAHDjh3bd#2aH97>pYj7@1i1Fnhmp-(Sr7N?SnU?z8GoH}30QFuL9E fEIT*+)p6OLF7v?Cmm9c&?qKkA^>bP0l+XkK)+RzI literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/nav_drawer_header.png b/app/src/main/res/drawable-xhdpi/nav_drawer_header.png new file mode 100644 index 0000000000000000000000000000000000000000..9c03b116fd7f6cbf99621021f4707fd8560b7db7 GIT binary patch literal 49803 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/root.png b/app/src/main/res/drawable-xhdpi/root.png new file mode 100644 index 0000000000000000000000000000000000000000..389969b201975eb500f0552441d2dd5fd116544c GIT binary patch literal 2133 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 KMBz2mlm7>Kcds%4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/round_corner_progress_icon.png b/app/src/main/res/drawable-xxhdpi/round_corner_progress_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..14bc52d64e8b3c3e759f6ceb6c7e73b897a4d46b GIT binary patch literal 1273 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$farf#rawi(^Q|t+#XcdT9rWw7Lp< zCQlbPkZ?JA&Pre5BWDc*E9=E`ZX0$bWM=Cgxuq1UwR7u@Sn0JaimgsUM_Y=&@6!{R z$^CQYk(oaG_{BQZjMLBQq^Hf_{C(z1O`s)20goPmLp_@LQKnOUQZG--yj_xDWhCqE z{_E~GyWnf(VOx78t4fS~jPs)m*3LHbXd3pZo3R6=nt9(#D_9%oOwey)653^pDDWTi(}hP3JkfYc+~X&rvU6v@^U+td-Lt~?c~^~+qmc3bSq zwcc}fKIt{ijWV7)qi*&*=U`2*o{>T2Y$2XmM`zD=d%QEl(!6*3qt(KXd`|x7% zT47uJ5~Gv_eSI%mUgT_fY_q#7Afcn5UYyeUjO9dUM~#Gm`TEz8u-f z_Gr_&#eGvV`xV>XRL|nd@Na0mWp&Ng>GzLkGcWp-nWh#;+Z&dLUEMgDOX#qJHUEhm8H1ero*I%mx_eggbG&9Z2yk4vaB|;w zM@IKA>GLX8m)k#>Utjy6)^V%5Jlm&FGamQ&h98;v?8Cl@1Co2CPh_vrs(3E^zPohJ z#ossj#b4chG}}M#s(KISSBcunt?AtNYc2)N7wG+(ps?QD{;;v&9{;dHk8rdQJN8@jN-b{LX{v9QV|tEtc$Vtl^zk{YU8F?-c3w67vcA zPeP9C15My70GjYKE5R2e^@IZ?#eaMIM{kz*lapnd8Vv-t8{{S0xO^7ymGGC~=TR}5 za3X{)Skh;i(ac$j%oERjtoL~S;-Z7f6j#NCYxtQbUXp5^a?Q@yCFpOt$Ex?29j1J( zag03Az_w_^2W~EIb`!y+XXOKK$tX)D7qW_lOWbhKd4K4W-bUXeOu;$^qSZbfs_zu0 zbw;EJC(Z6ityA3A8Id8A)c$qPD~6<*9n;r3nW3$r8~=@Nul5~&8k%o?f8Ps_t_YAb7Cx&nf5nP5`?#Nqm%GW#vd@UO zzE$2grRSsBr8V>VxxD+bi3yZOvV!uJ%Igc{u0#qxzaw zpO&W9)GEb4dF2s4CHZg7g8jKKH>=i~pVtMISi}H@$bU>0EU!+cX>IifsrPjCb6Mw< G&;$V7hB=l1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/nav_drawer_header.png b/app/src/main/res/drawable-xxxhdpi/nav_drawer_header.png new file mode 100644 index 0000000000000000000000000000000000000000..26775141da6e898e5544266ccd430e346da543de GIT binary patch literal 127811 zcmXtfXE>Yh8@A$S6I-khwfC+SRk3Mpr4+HNR#78rRV7yKz4zWVYL$w;YSyM^QALTp z-uS=o`yoe;BOh`<_qERRIUX5xW`MNIYZd4!do3BgYJQdb)ZzxVo{bL!qp0?yh!koNTeMd=|3w z?G5yIX(8t;XhqGiphQhqJsJX5J;lf%>NrkbFd?}{7&vd9M)x~~vNA5?KyDaLbaYS@ zjV?b)1koJ+COAJjq#!I}=z7hs;O)=$^X<{Q###B_l_yzWe&BZzkR+>#=}H8VlqrHJ zeueb+^sQ|QDg+a;yJ3+Ne6az0+&+GUbrB#Z$H&%%{|)Pr&lE8MR@d{4E@39*Jz!5U z&FoRor$=3$aZ;KDLDX0Z@My(iECrQEL0PGs`dGQRSeC=qRvTD40$7$@f!kA9L0K1R zevhzB6Tvi(a^tXAscgfPupDHuDt{P7C}SB5V^P~__DEyR31A7UzqC`ss%^&V8Y3sJ z$08uX64nh1<-x-B!?GNB{MZNUQyLbv%JECNe|**Czj-m1N~@P^VV718HYE^vhyU`W zAc%WRosN%M5{3;+R}k#>O`{hL76<$~8NtHJjibTb_T<)QjI4TWOftHO%tBzV6YrkQ z%4*|o>qmu~0v6VqXTZcAzt9(kAX&U1=evB416&7l;@tP=k#_X}g+{F0-G!GYZV$Fm z&WmlCpWoWtoYUx0G=&Yl^uM$3w&;3!>vS0)cXNLF>)Seeu;}YxRoshT-6LqNBBmel zgrBUIwqsOpnu+gkn5Wo!)U8^Kd8s#aDc?marAM8J7O{mZ#`E-YOy1k9ukv5x$gfIZ z)jG;Q@{1EFeQk|%rpZNa?Af(`j)isH=K6h_n*b-sA!O}`_x<71Th&|+tROq}_;*-X z=1S~>218$-_7h-XDdh(9);?oIe+Tk+;W2&xZ|OVnjiux#MfRRtGnQE}LeYgqnz6gaNCe8uKy49a|b{rqUWGJAftn@dnYZDv}sn zuM3$bX@s_vK4JKzl21Ht<0+CJLz|m1?%u!viIU9wJzn7eScs8__6v9I0P)3ooP-7Y zP>OX@{DcYC!*y(xBo%!OwMLaVZQ-ZZKkB@3DT9@}2qkg76;YPdiXe^E_#gkIjG+=D_X~seJYlB{hl+Bw#A2)0 z6&g%ZPO410=sn)$FUt^DV|bh}xM1_P9$z|8`pmE(U2nNG&wxTA=9OYqUXxypF0zzL{C&FgXb7#kW>Hms z(iDROox}N?njOzpisdBGi{-7C zml>C;eazT@S-wHyms!C#v_l1NVZ;@mSDAz0T&Q*?CsDJUFF7!1@0a^r0f~Bl|iffsWE0|IF ztOAI7(`UB9zTw>G+*iPf#|h@te7^VmAYJcyOEFKetd^`+|LDkQ{^-v%i42IytmyBI z-x;$RtId|Ld|vT4l{S5~Rj=QCRcvz5)b0}cO4_8z*zncsFDg}8RUALts@h5@N?Kkd zyr{_jX^*P?*EHBv)=Xc!HHWuY-Q?Wl53{$Tvv2OLMja${B>1=bf4+s_lOpHIDwtm} z2Y58NQ_C31tfY@+9z6B$MJMkH*&F{L|1l@F(L^=O)AJ@vKVwSOp3c6-BjhA3GF})2xgy8Qp(N7i$hA58fOgmvTnLb9k~%#5~4P1JGZ( zTN6)-bCQJLu2u$UL$!nL8-G6*vKESaSv6bRT;yGT4m+fiKa+RQ*^u9md$J z+?HoF&tg7Secbw91$G$NkF5fq|4to?8+$_*suQsDa~vPG4U0qtkZ^?}Lti4EM!SU> zGfar2NO(^yK|NNLu9T9MRx<2Gw54kVG$eTC%LN_f7M`?LUdH*>c=o6v;hMA#hEj2HCcSdI5NJbpi}UBZ{WSFDJCtK}0{S!}x&sn&9a zOg3%07pt=N#+ybRFtUTS{ywtLSO5`C&I{ zx4XR}kZnqqT%NL>Lgqp`ztBLsYSi%e^h|kCC}os;u`Q$t+EUOd_qE-&WN>U$_NhUk z0ovfoK=NR6xe+#d;WBZ+=+}NGzCW{Q+J1g3Klp9Y@R8y38M_(VYxV0d=TED{%Xcr5 zubp34y>Nc_w;yK`=NA7lS<`IG^bGSP#6U0RC*L1cvVN9tcUgzt0;H0phMzP0)*rhm z`EKr>6Rw_vNM#nhqWmh?@TXY3nC+Q`92L`#(^WF`)2~gfOk1@_nHf0q$J4W5aFp-dqP&?b_`YXobJe=Ap~TnWv_AOLK=y zfsS3?xhE4#s^@s2AwTZoPGbPj|6lC0)0ZlJuXh_$&Iy`z#@ikvRbR@J;%a=b6{Zwgb^>tuG%w9GoXYW+O1d0 zo4Xy=8kjnvSVgUT%+$!O%YSR(7|zzHL)jRo_k^Q~JzLD9wdf~WOZ&Xx6){i5`YXRW zqulZ!t<7Q57Vs)@4$Jz=z}It?o>R9cPoA7#Fnq7{+kpG{_*9Sts?Jbb+5MIMEJB8^UOd|*`$LisAKu;2q{7$H$>a``w@8+&z&3#M;+e%_ z)8D6_c2yl7zsK-dJ{%Q1y(&OG_ai}B(;PN-&u;C3RbJ{|YqjN7B5|kQpsvjWYyP+f zdCgv3o8Pt^?h0+ZKJpA{+!;o`3cEj z()gRbe^eX1>d9PYJIhh;l>0bxF8W->5e(Lj(9d}bsRz5u=_uQTMS-CPq#)#S_wBiOdtldbUBt4ONdz&D^nW{R zZ1=*2^gFO=!`2*)BIHTi_KMwv^!|7H`Q5dAm`}}mhtWl=8HD6|EtE{;N~;Zg9=AhJ z$xkd>h%9(w?u^#C+ly=vSm{s;us**NOYO>8qHVOl&)~RTeBqPPg{L0y{92yH&-c|~ zR2}_Bgqfxcd@i^%8zgB;KmaEQVQ!2t-oVFsmxtd%<)czx$)*j^BvNDdN4ss`#+<+J zw^cs0c-_P}7i1hlunU`Yr+_5B%hbz*s(3%qcPYi4d_>pX?Yt-H1N2j) z$_p#oft72Dt{@`5hpqe}y7@bLSQ8bsEc1=worLBXdFY9|yM5T&=lOTAeG=)zjFJ2? zi_6>lC8zN``bm_XCdo4ZthV5%(pJbxiYI-`h?)13mCnDF!64obZ*r;8!jp@jt+b~7 zcjaHlp>L~1?0_y}+5lVkWGYU6F_~tO53Y~gV$TLiHA>w;pT0O)ldOb-4wkPFd0`Mp zWw{2*i3yRcx=em;1Z*QcJ$?I zx+y|3Y0G|6W&_`n_A|KQDk zPTxf6K2B=^JhV->kMFHUWi1Ur*hBqEvMt7qadVn_-y zJuSq}*Uhb(d$=~>j{rnYIu}X0119krVK{Gw4VrBih2%+nM}RB^9gpi*V-bGG@SF?66jgi0RBm-jYfGhMmjr92 z(7Z6s0%B%H*(OidzEFmZXI))0-^roC{1L^K0i_gmOOxZ{*}x9aILa;^l#Ny&HHFWm zs4f*CC3e70RX?SUB?isxA}loHLVIY0+DPEQ6`Cy-!0-!Gc~7gB9T=S_6=N|LbHqgH zCz_=Qq?K^;rOleYDEer}UuvzVk1^;tNQpKoOh>6NIr>*js*ZKHlPxxM^XtzZTG8yu zsLXd0r7F9ylN(gi)mjiJn`8&ZHTppf@OB(J_o~Vr*9~0LemoJTIRVsEvxK3?Z^wf` z<-Fmka-zC=&hu>qS8@<0K?6kqhvtM>_1K(x;fjhZU9qL~UQQ|@O4CpU$w~;B_BtkZ zf~eeUOjm548d|Ad_3Il^xFm;GMr=kbh#RiI@BXG^t`o|qThm4ahx^oP0<^t$*9L{p z+^Ae1Qtfj_!fHk%`4=Ejls8mMk3^S~HBJFQcSn6ZloVG?Wc^_shE&~M)A9Aq2HnlY z+|IGVxpVmkcNXd#ypJ8jRgO=#C{zT;Nppo|J><+8cJj(2+0Kfs(H3|r)W*Zm+_Vn+ zbG!IUddo0e#Eu`E^7|Jp$x9atfqT0!2@Sv~Yv8;boq_Zt$fxhcRr!UDWCcWLiivdd(4*GBkqpWrG@Ko8IT=$d`!y0SohZeraXqL-zyYA()EE9`N>yz=Y) z*+=mP2xibvSW^lfJoRiLv1LtZ@2Pjx*K8dn^92ILz(qpTFzdWJ@9!5hhWop)FL}sm zdqHnv?N);=0l_66ghT0ycmNscL!yuIuB;Ci@dk~15o?vnhy$Cg8Z-`dN>;EwF`4Tqe^I_L^4C1 zvcL2)!+jmmTj~Kt;C8Wh4D0wQ)-VOQRneWdylEqReyn>PociiekF#q`<)+P z{6p)LO3U0gU^Es@z^)29D8Lpps~WWtJWR@AS-I7lKpCXe&Z*4H&K`NQu0`1e>nNSY z5zNZ4M`Qmi1F{hs5m^CX479Gi7N@CVH}s*zBTm`@Zb+(r+VB5BUEMjzdtuX-0ED-@CG%cJfkdl7>(QAa`yQodJam zIdm?{cO12b96MNQNyr3owoKYe;zt3)0@h*Ac-gW{6w;;X5UCncq3+gUQKL4cR+s|! z8h+8fzD7$SZy}`#$aoDmrXPm_onS;K(?uCZCd$RZifhz|G|vFODf&ck;OT8l(N5lG z#fjE14_vKn=9|MukXO#~E^ObEm$qMvP$r5?ledpFDVGZKFzxRqP=|r{*H8aZJ=HZ# z+D^}IaeV<(4s(m*Zkk9c5&Po#A0jn^29eWRu1FU8`i=ESEJ)HCU0d`1YiDf;!k_2_ z7b2dE6b4dINEFby!4yY)ljIWtx>^@Dls*;1aUeG5`3>9U3Fm`GRpwpTVpB9)M~_E& zGBWaCIo;qNS_Qm8ia|#UBhlsaj_!|y{atDR)tE2@WSKE_B=(B8HQmo{m$#7W@IBIf zEv~7t^mejOP=G$;-Fuoj*1;OkQ<3WK`X%d3Fa((?fF$nl+j!-E%hw6d1d&C6&3&S& zpLaaBKM+sRZbH|J8hGc%M(wJxVSwW2qn=NSBl~|%7!tun>++0*IFtpFoa3kqy2yTC zZYm!&7`pbJJRF40Y}b=CnO{+#2m7Zs?T`O>LQn;M@%#QJ$NPgjPjGlVyU0otwUyE# z=mzgs$IpDjZu;kHn*IKsHevj)gTZ_DVPLwA{9W5mAuD`iLM?Ym7F7XN@5H+7cCL&iVFT7Zld&9BUyfPFVmesjeQ&yinQiH#});QXD`Zqm~qQP zW-ab30&KAX9;(RscTffBeq?$0@d;(`&f3j4_TS;h>~&62z_}2X)1x_ueN5)%Dyz$@ zNsB3Uf_ZGh))=3E9dDzTHQe8U2_;O*X+tFGn&3P*Msos8rG`El_r)YG>Sn6|t)pTutz#oS{zJ=L zPBRyU8cPtasjQ$+e}j5!R8Idw86c6f*z&f%qWm-P!YQ8mW%#swyo}gOv1Cb^KVXfY z330QEd4@#gKC4>AJ23rIv>S>DgCQ`kI(N}u2nb{!*Vqn|f6V+CNX;@DHS5p~P5p17 zgx7yyYwtvb;aFa*n`I;y$za~C##^nU;(R&oyh1D|tpV_Bo(}~PU1+K+CAXNZ0weNu zd$qTARiblA6NMum=OaOTa=$~>^B3eXK%XYiM`c#5ls=3>k;Z&2Np*UX3BT9$jay^Y z{+CbB(6uWHUH)(%(Oe{#|Nh$9+m1ocfa;XVDb!Hxyq(BH^@LWm&qk%Uk7VzUxDRUz zX0CV8nG6VqfUc@iM{T}e!uw3$7p9G$E$=&Aa zl|M%dyHlX4NoeTo$3mC31$h(U{G(#)`)7hGo@@Eueuf|4>sm;hiYcuavdhM)%Ui#z zgD`EirABCD(=$q0uO zE@M01j=Cj+?tbAv4buQ5SR@_egw+w6N2NNluTWgo-KWw$&hpjO9F(kkO#1kiBJ14X zx4TOI8B5JOgOYd=-MM(FsZ3&nw^LAL-os2IYEp* zo1-uePHCFu>0vrGr*Eu?MdI$}BFmEXdDKS}!4SDCFH95Q2Rf#P><7B%AwkkQ3P=XH zrZRV8g4C~Wwowj`mbO#U`!K~ek#Oe!(Yo;Wv^b+8tZ@}6EwS{1Xq`ZrFORRO)a=Pj zAb8NtTb@Dp>Lq?J{O6M$5VPZMy894fh#Y+Za#S2rDQoq$2$}RiANoeQLQSN6eJAz` zi_x&@rxQJQ1>_u6^?64W)q7kKS^$&!c=}a`*OIfcBas+SERD)RqW%&L7C@~g9k!nr zL?X#T{~^n{AZ}|6BOxGAA84h+?+F+(?I98V-ve;BMQzXJg$+)nwWJ4w@$M3Npjt`G zOyRtsNmSow(AnLp&B+2iITH1B+1fIX>Xg!Y*x{Xy z+|&kyftOUoFFc*3i<-2k&~7p8V^h$^VC344PKb&JyXvb{d2U>fuF$t)wD7qZ2!;eE znOW5}oARodd2qA2#)}4n`+SwTl1N0_6ZX+5bX8IsfQ&8lQK8HWz#9d?lO;E?_~qcW zTbw5O3igDQgjp5z*TyZr%=Oz+EQnb{#OiRcqO{$Ruyb1b&RVu-7seKQLh3(b>^+5i znWxnju;Z6>7-5e@%rcUmV~+O52I|7VVFo~5XKpq>>+?-3=X%~aBMWKhH0u!4Vj$Yf z+J0wE9T#pKj7c@BCjLB51HT6zP{VCNPngvXt*?jxldz7>IN`=PEdrZ?hEIpM8}fKT5wo+b+ym{?YI zPo{k*_kF^32q%aXJ(2PMhryED?#`S7Fki7ExXh%ZGokg@wct-IE0 zq`9@$QH&M`xC-&VvNYvKyBT=+x`uC7`(^^-QH(zq4OgT!U4yfHY7Uy$kCs{cjFztL zU3Vw7Dn~zr#Ljg1ZQq8!OH%%A)7}?d(I(`%EBv9i09{LEgvlo^9QfQ|3DYE0?D_u- z7P~!(y4uYxYz>yayA8lQT$;W)@*SAdV4|DFYpJ8y@tke=*Mkc3tP|*)V-mI^VEZ>`#O}BuAJ0a=e)BB^O!S1dD>Qm^ z>1F$|-@OY3#OcE>>^TP8QWGQsgnD!fC?F1&QdGKzSHfO}uvhb2{0*su=7uZ|VdUcFG1@KQ$Pt#$u8ym+sqDJOcuJsb z)oHNQ$pG1A4bzCBQOo;o;rI}_w4#STjD*(dGqF5XUudKeSMNla zW2in;MwQX?(1={5Mt!`s0Wrh-44xnmEv=6+uMB@GBQ4IX0-^1;J`N<4r5u!v=>|8P zHMvyyMX5!6Wxi}+ebspk%pcY+OGX0pI5&k=7DRjVkf(wW@iug9n8HI7Z=}1oxz#mu zf`O zUUHr(BS1zNUco>@fg#Ts^^Uuz7RKWFtG9x_yU@eMhNd7TY{;g0oO^=SJ6A~>Pv|#{ zpQr1ia!{wyrR2=p96sqn#_w2!6=3eN41Q`Mly%UALS*_6-@&-ej}%nVT$?qvb@6sg&dbHZ%S zVRhQc)e$7hW)0y*pF=YS-Bg5ofFKR_eqI~cv|UQz`MpgZ^7C3D(ue-zcX$Qx`W5u} znBkMYVz$UXIP15gfMB=Yf~OpLNI};C(X!DItEhTBLZ^M+{*Q~@;R5?x`5Tq@{_=auYPX}3x0(2RCqeitwQ?-+JyRL6mm zD<*-AK^XKN>E#mgTUwq>mgiUZl2#|e3I_E#k?&GNGyvCIB0G6qZN)ZU^YQL=M&4-m zlFqo(|LXow^^3q%P!E6hxX6l+0$|z0ZEklh=Kdl^C?8q(n*TD>)^JC-yfCPc z{UaqUj+Jq3RWhi!wbgp{wny^&4D1-wUN+Wi+S|<+ycbnUlL=P`=BIk}&pEZ>K3r*p zmzU4iq&OM|D*_}S>FubkxrO@oZ~4@>?)t;DGfR-SimLDd(0{-F(hdIR=TkyTx1JaB z%#p(od?M!Xxp393fHC%^|LTI2LYX;79B3=}Gj$)BEBzQyD9xXgL?oUoNki7QlKuge z=1owVZSfo3Mcxe`HCcwHhiC$}Cx&)lzbatUDcwFoZK}I@%MyZc;LOtr{R7o{B=EKJ zU$Ge@cY3_-@5@)R#sho&=p&(<&`#m|Z7QkhP5X~W7f6v~6ZDUnJQ2>qgjkr5(}J~! zX7l<=b_a*`mjdT*S@mPSYi-T`uKLU(k!zkF@2mO9&Rf?9g<-$a#P|4k0*@#Xd|WJ6 zBJhnEKJYESYj4edS@)$*Jjij(}?l4B% z*2#cNh2*d&L-ERaA%swHVTet1XHE=LWNX?nofl;YdQ_Zjb2gedS;getnI;DRFapAE9#R463oG7auP|^3BhWY>to!LD zXw=+}6Z&r_ZzJ|NUEMdHAd34-Yqw618q7k_+RGIWF>p*}{lV(O>v3@J02t~2h4#U} zvp0sR;3CRJ9$pox&rssZ_3D~hY#)XE(A{8P)^Gz=z0#-S@Iod)qdJ_7B&S1N*s4(A z_-y2Y?(wC_0Cz=?zC9k4`fldBO1At@o{0{}cI#QU>n+PeMfhn{=M&T25cgW6*EQ+G zR2ohWX)dzC2tIJZ_D2zLjb@^Q`Zw6qznQ?S)arhGJiqLL{ zAfJE$K`6lf1>p8~?Gf$idF9%j(B*#QtpYwY>Q<6*!-?XEVY4F-7-+B^rt?67HEM^p z`oKIz`AR&!)-kG>3l`r&ZuQiamE0 zCH|o^`CdD|v@~Zq2OTR|){kZoA|lBk>jJwFS< z#T-S1(a)lDd3~kjlF5qzP!Ng69=KXnCzN_qqTdfcD<3J&nMSDwD3GBv!39<0Gk;{H z41>uxDZ-^ZsvQ~QM-bxPj4OsJd13z+O5zP4LHRSly!;3$!*g;>;_>~s(NCS4>|@vC zty|^vKO9?VOy!{}SIb2**$+=hg3+}W?uj6xogxA$$;9e%19sJyor{(DB0f4ma+Dbf zS~zl;cdF{hs^=3V3?g3eO_iQXLlUhP0ayRyp@8gX+S6gGt4Az}oYoGs9{)>i z+t6`1Ql~U1lyL{=X}+lXA<8a|)bh!|yFh5svz@I2O%AZx)&IVXI`1*gybZ9cwwym6%( z21dq(ZbSKfq6PrPz$4InI0&jD|)4xhB-5WYFb z28v`p^NHzkeI3&{WF6M??A*bh&=R%4WK6|d76!zO8N3`YB;GeZ>Fl1iJIL;5^ z5e&3u+FjGW+teN#hxQZxxN6|>Bl+i&$8e>tMdo{Op^r7}hq3{8pMmx9?I<}|%%D#Q3gh7R#gTmKz zyO(@JcVJyPEi@7=cu0kbWz$eYutIFd*Ss6C!N)=GNQ(dxrDekMH$Bt3p1h(Jm_$+g z7+Zp@D@s$BJ0jl^ev79|@*IaF^NA|_t2ba@J~JoBn+2ohp4=V==0t)(kg)kIzalc$ z$tpus*C&<_0fA&u(;yPvyZ17(y4z}>aej2{hCV2MuPW19cnSqvQrUf_Fa2u!DJiG* zY5m%DN7&z8rbK;0$8Pzj$AKn|=W8JFxwa|QU07k~rM`SHlHo?r>t1pSGceRj*wDmL zSktr4LmsCO&of(bu#2ROB`w{bBH8+`+aA~a5~b-Uw{E}M<``mv%B1zGGnqaNWvT{v z4;Md6n)#p)ih1WS^?{zHF0qxJP5IjT%|g4~d4SI@?C>c|5i;%rVjTyNRDe1rhdv6= zw+szP=7Ee{F};u-o3ZL)ukgEH7Tn_%VW^1(t6Uk@v%}l@G(~ks^6*V>f8PsXYHJgy zqS%Fe|BxFDpy8MtxlESFg=igHJrk*TMm&y+McYHr!d?sYZok0eFck&xs*}zyMOr=t zXe@{8>c?+`jIkn*WU>nH~q3^Bcl^)5yw=<-YJ;lAL5WL)+~TsZLe zf6*FwNL|RIwJy}=3rpG^om2uF3cLTTQhO8t5@vaWhiZhbAyFMg%-RDM%eYPdVfrA} zw}=6EM-Cx`RHfhlqH4bU(+BdwVYA1DA2e80ZlO$vCqT5DZiWw9*l_|H>Oz?Er)ZRJ zwA+4+?jsh1qwqLNxul{AJm_k$Eg|}sHQ^H*uhs+Q@tL6U%X!Ix&0|I~0el#`7`f1F ze4!c~#@6IU1}R0bya1jOVw45q)q#RZ6p$<=)jXoTzv%lG)k#9=1elw|sZRmWp>1ur z!DAV@70MP-Rf+@?DFKw=x={-X7o=u++#X+l(EO|_x<}tX5E~EKf6{4cpkQ5U`P_!AKqiYxt-L?`ZoxUTI?47|I^ zFSS|z@s_*HCS!5DbcwE8JgUa_+qqYy_bbqRX)+z~#yTZAYSeh)f|;k9YQ=R5#Mien zT6Rb0?A=f|tB45zlBT-83D*gx&jXXJyhKsW0-;3!9DIeh`N-h@W&?O?dv)x2ux8Si zN~E6cXR=czhV>ctg27#vAiw5k)>?rzkAKdgPJ@&=*mZq?|Ag6IVh}A9ol6f0wa$u7 z_7&+Vt@v@XH=3K`m1^TFSF2|8x8|Wcs{JfcP*douUREjTD}6qRjBGDp+l94T@W`8E ztb5pb|BVHr^b7WHhkVHmlDvEHqX%@*INv5R8N};_KH>^I=JGwn>t$6oWEZiBGS=eG zKX0`RI~e^rNbVaL(Xr$&{C#|N!QwjbL5i|M$pRnRmBo58o$Rppg=X^l+hI}Y2&u#VZg7jwO!Cg)*A05ey6 zsy_E6@OYWl6^5ZJl~wHdWAHFti;#^DE_<$0$`$pl|7%p&(Fs4~wu4eKUOXlRHoymy z$bW438xUFFYx_X@{~#lAm~j&4uIjcOUkhQQX+N2Jnmc^Q$3gV7o)#z}%t5I-U=vDV ztHD;yPNvJ61BsOCnfqR~8v*|Ka3hKegEu9(?y=bMDf-F`c%Vf7Px|cl3~9nOCNz+J z+TwhqCR6}Dg*y60QGJx4RX|3C5Tc=fW30u$riU5BwK9GG@S|(J8MK>RQJ8bANHI%= zEHz1-Z!-9b_uNd}D5c0hkgMQ(-eSopmJY2W63B35Sp@8P=V+|HZznzbu*5+^} zz~hdus;8#zBy!Wc?8CXJ#x)YJSX|f+YSHq(5r~n-7;L=viWfYiO<_G@Wtgo2b~d1u z8V=;}vTivU_!;mA)YN&^8T4R1o{F!alQ#tvn0a4SFVhjL$xW<&(nRHEXb46GY%nEJ z14E*+n%cL!P*IsT2IP4`20FjG=}l=MnbJ^E2Dh?B!;Fc{qaUxWwC#nJ8cE|j|Mo{~ zQ>)wHVQPn^YGr!s1bd?1KL-|bl8LRciUb;!fY@5da6%7zd~~gno;Kk3YTmg9|J!1u z#!}Fy1M)WH;GeQ&I``D~iL_yB*d>k_J^Ait(5h6uDP!3v@#edFpp1tBf*XL(!-4>A z7@9@C@O`y>BD}GGVu%unZ{y1^ofh4LFW^hGsg@GR$(H^zVJuPEevKQhA4D%CCjWjH1 zj*rU6m`N>Jb-J>*(oBlutRP(hz_qlkEA%P|RO~MS@cdLyCO1OHJOmAHuO-Wcg$0A) zneT^GjaO9OuQYtow2y8j!YEiK;K3{^qH?8FW4}9=dy9wiH`2?iQJ&R($2279>x$MLZNr*sgFV6_Z#i+}IrcK+i5_B(EseOZ1iT#h}Opp?3-o_n?7exBe6a6)*pvrcG9}=hh>g* zI{ipoN?H07ZE}%+TI%&^bVrK#?Il6GWzD|w1?rl*s)Kw* z11X|XDDx7)_3hDpj3G8*R}A_iL--2*>}``kwT(j7Yn`)G_;ZhFcUJvi5l(BikLttlPD8`! zb5kZ+4k}T(gBI85W{>0*Y}vF{O6$t6o=HWR)xAz@%{4p0+mUA2yi#<@&RMt+?!=!P_ust=jn27zQG$F%{&3P@_E&^)5A zHlNAf<1u0|XHAa(Glc1OPZ5x^68rM~xWszNf8;9TFgJzjOU)+vpU>#u7Mt6MMW{sKV%MwLd$}gRR1qr@ z49AcbI1u1gScDv!Sd)R0y(A!ge^9Tw?amS~xA=Fl`SniTId#<|eqQ!)?8o69;J&*S zMGDOF*DBUc(6347q#knAb@+3zwXo7kj+Kx%OOyy=AmHZk{yB@gF~v-SHh}TL1(MD) zJAUQtEYw}0R|zQ{{rt)8OmlxHty8Skdt$#B1Q7*;j>kIN%{6=)`M0*nY|CTH%G-@T zm_WsxiByNx0Qc(_H<7_;x7|wGKq4M`^+$|)r%wvmhj?W)*^ZkMsm(aUL`%-Rlf|2~ z?{7P+&#VA^Q~Q0O?Eh81H`YGiKWi|ixJ2g{iS_%fu9K=o)m7X4Q=$wPp(YcKsuC}W zz$oHL6cDr%7CKu+*k=s4HwCo1mpsECkH?L4Q;^ z(WDsO=4yStxqAEOg;0?wd@2 zIfQJiFoY7d3oG}w1!&trf{~8QuO7LY(Rg!*>?hJaN|A+l%4ylQH#yER*NOD>g~3}j zipH4yXoQve@ct>K*}kCcOA!$Y9F@QGzHi-gPkt`c)_&RiQvZ!tB$(|CQwW$DJR+Xc zFYxeZ_58FV8+;dS01(M$5Q6e`&&&nA~AfSKE4Z8IRUoBpA}4GSTJIT zcuFU>ut-7JMOLdyiIlaH{{OjgW8`NETrl7BaK1>;U0dMm(>kQh9;r&9Gp$$_aLQmB z=%MGlnJNqS$pa`qh%c7-DQ5!$gbO@%7DQVX1Z-uJbgY?dmkZDMiji+tTRIl6m(7O= z2lc~st6d6!7C8=F(Du4r2xD~`o9|pYrX5O)J-wJBQ?TS4@_TVTD^w@? zSXZ;SG80_P7Bv_NuHWCjbANkyn{z(|+>1})gU%q(&NK#5Wqb?ZaKsDA8NicH^|^83BlY%D0mbQC5iVVQ!b35~Q;X61;kY{@4aXhW{abak&IqIWI{XoHb%L3X zM}8v(s_Yt6l?_Vvj4x#N`q@j5{=~)^z8Z6jsO3cfPj4nTODPu{|9OX2LT;PyhsaOY zkTc0wkl#TF2Fyzfm7kZlg`U>L8ng6(jFcYs$4n~>^_%k=KIr_?&9jJ1yCr4S+eICf zypFCPew&Mg{xHEnzCc<$yR&p2igr*!0LX0j>7@dRs%uCt(l+O5R~R7+$0tv_?|nq{ zR&gJBqZZ@UY?{tc_Zxu=&yY$p2s{@@Rxc66Xflma#*J+okLOoX9r&tNhES#oN@<^DDP6j+S@Uf6}#O+S}}^UpM^M)WcT_4`ZB4r;0F%8)0G$ zGBS3IZQAx-*@EZ4v^b?FCli?VHk|CQu{2q5dryd*m14^ z5bN+tTr#ZxW1-ERzu7=gD3th}93`vHJ;d)-L{MaEa@~kA)bm)&-YdFnCmg6AvjBu59U{a6{X;8*-m z#n@q(nYwt2{D^_nf8{)NtIz*q*Y7ctz=*a>g8;DTFQWj&95l1}PYa40psXc9lBUyh z3}n-MN~oV?*n;!!kXOkMUAsrXIx!zo;_V{ue>QDk6IRM;N@4g~BXvhNm8VPHV@e5zEKW@6NduS6FuYlVPk<8JVYoKXc9m_2E7*_bUsKPcIXm zhKF?f;Lb!>r3&=zm_4{Q!ZHlom5zh3oc}e4=Or$r5}%~*`iK0@1?mtIa1m6>QEyjJ zqlgGAm%sN}Y@WY)N^e~Wp8Cz7F=|~Oskl(9DErT#R}6s1H-=HnljxwHCdwIDk-UY# zl`8ax*cfkOGzc%7@J}JKjyatD&F}&?Ao3ZqTmdgPPnO)Xd14M#c7(cY{ok{h7#a>( zT8gQft#w7r=Wpv^k~;1x6y2^0Br%GT(D&w6mrqrawZ+IFaSE{V zN#vUx^uLv1la)^jfJkX=+`DoMgi!g1$aJLco}kABO3=d>ExCg)4ighAvya$B+C+sn zRJmCA$lC9U)lwbK+!~p|gOXi(I|9#=1u=rerzc^a;g`;{1<-2dCA9V5*}JXIO8lL1 z%qs;ErSqhUOP|=}eb3gsb0D`pGqWxRp5KkxG@BX`PIQS5Rl8sF-h8g&Pb+^570|UU z6U{Ijpo|k)?6+o#F#a2==4IViK!(TH?W1InKdF+8SKg57%UsnJtB@#42OU7BKMoy=M;*WM17l20I&ocn#OLa%FF zXMs2oX6_{=RxkE1ffp{>EuLZO|M10um=}3f4xbZZ)?gMYh$PW|?gW_0YaV(HZj*wU z`V^Rp@8lK!P$)aqL;vQd+erScsp5042_TqK2Xt_GWLfGieoJ}Rsb@+`i!>6UK@*gAG3^=TDr+ z>KmuM3?}s6mMvp{_oC{Ut2&h%O>@WuV?XY;Y%!(U_}g>)XL9DXOIi6xQtOJc9yN2j zRhdPUXm^EaG}^5=_!T_$=C8@5Vb~QB-%lLZr=rW8OR>iym5$y%Vkr{#Z8hv*8I>z% z+K}+(DmD61%sSic&r@TOGhRZEwBA;teVc&OSd00ZM(#!TTqIH#iTW|~3##ntbwB9M zBIm!QKWpxvMv~T=@1fb}IX`r^6c5HMLhqKc(OZvQ`!`IxL~y>Uw`q>4_$&4`1G&8B z&4-QZvh;m*yPe@GieLd1S+`gGF$pyuW~iTqCQduNpiEGc>82z^3r9)u6WMT!I-8jO zf<>jgp=V2w^D2v)tJ_yxJN^tYLrw5(f8^z4(1jt@@6I?2N|TkD*k=7+f6E`SdU`38 zlZ!k}wb6hCQ)j#mnv?K>-|yaDpY0_Pj}p>D`}AyO{1bx*hKxje$0bwd#MjYiNzAUTnyH}7ksF6D@;`Fa z2mDT6j4X696sb4;xS5UVbE+~oTS~nKr3p=NV|+hYU+P36%`SIp&0}Qhwg3| zKn9SKZbnKPQA$!kL>OX}P6_GmF8Rm+(%q#XsephZA@yv&zw7_vd8049lY zUTf|5d54-lE9T|cA(9tY)gvFH-!Y9Fi!ec%h>~J=Zz%TRY`of3+`d1IOI~7pS5m~Z zo?EQ&2|c!zZ4no%Ax2SInLquW(zTqz{T+R?My%pJoa@kIMxZ5ZOf3-h@3_(B3<-lq z!_{&;sHcE-l>UjiY#tgt@N1huvGzT(ETX6$qLn4%qyMzk-TFb!qXPHJndjcxpXLY( zaXRQ;hp;b%qCsz(!mPvdgwCsOXop`PcUqs&ZIxkj`MjmXG6{<4Qd??gJ7v9N1fk&* z&JL3tj)k?KgK7EKQbLP&*5_P?xBL2HtHHN=o;5>(tD5J1F-xznP)5?U z0y7h7y%F1xwZJ~2`BJulJ(=Hv#f*$>?jE^A1`+xA#F>JgwHDoG#6zatHqpt5@L%Ro|ZN&i| z>G}xiE$N8Jhw-Ha^o$?tEx_zGolGT_T)((LosmRtf_mkz%E3W9yZ7(<&tP!#YA zLZk>2Ms~-%Y3hrT46h~zJ z{QQ3X_&L5FMbo(GYDP6?nsdvI>TMGtkzvlktuFfd6xA33-ApI0gj)uAZ~yP-cXKMu z*WgE!YzcUyYD^s@{Jt@dVktMx3+5l)Ys~z?7Bfr~#^H;%1z4MIQb?8u(oZ6{d6Ayy zWkD_pL^xTzgtIkuK;)ziNnRe{`S0Wl_UF$;3XkzQpw1(fd_X4CcE0{*5})>8B7N!#bwr{wivJMt*o_~th5 z3hRg9r&8Gr-XNRjR8J<2OOE0Wfd1Ah`-Hyt?Kfvxg6?;9m-z>}c;>e5au)1&Z;GV1 z5WfY+(6-^JRnSog7MB0077Od0%_a{ks)91*7LJw{431)*6qwhwy?m8xe7QV)n_g2s z9RIW)@2|DD=*Vy0Jc+7O9O*Xj3xhIMC|rM(b*2}^S^+@ckYpsNIG!=A!L4G>SAfel z^>ZlSeeedR_IXb4G8j!O`Jd`&wkCCmcS14XjHhCO1gWeZ$ooE_C4xCcl?PwU=wIAZ zGw;%%L+bla#?f3P@Q3x%XQ@n%VT<3b`+^5^mi<88T&(qU;O|56IZQV!Z3&+lvR?b= zj8PW!K%I#LQ*2~us;KUrvUWKo0Vq$l((Q0UEF`v#{uO+iJD})^mv24Jb*wc|-;aYo zHE#OWAh*5Ac$QJG#DByL{6+014>GE;W~-;C&Gkn|y9=PTfC0q3y%wU#VcqGufd^%} zZLk)!e&MJlx<1fxJ}~uerw_An=&-YrZQftxq&7m~*t#}aa_doIHp2e|H;iU9O6Id@ zI*~bdZAIR8qk@(REZhutp3vmPQ2iWqU`voC3S{JZ15@@}m`33QWL!)VkBwncllc6G z!?lBgbjw7Jz8gYY*>OKggJMEm^nI_FVJFa4G;|wk-8L0Zg-kC%=R&I`zeo6=4*RQ) z{(W-`X2&N_3zV9_$0re@FSa-0bfn7&6GefWj*{`FL`=nws1(OlwYm^^+~hZjSJX&My(H-U20BpD+xhxm6-YkbG%o9jOCI8rl1XdUK(7v$pJGEwzB(b0_dY8FdsedK12nomD638<-1YCEtR7{|B-&Rz*3a&Guva5}`hJA+xhY zi_3K<9=j^9Pw1&S(pvS6LyZXJ4|($KPhTd$Hcrp9XmYP^o<%fVf3#G#Lt6nutUJSJ zWHmU)mEi&F%GD?M@iH@yAhbbhhj_QaN|^Y(;E(;BM>u{~-9XE1;b-A?`_kA`PxQL$DS#+pNH95sd^Yja73Ns0h9AFZB7PMfCxXz>Pb^0sR|v|%9bb- zI@R1S?kkyjoFWs&lDGr5Ql{LGyP4ym1~P+WV)-q7&M@S(bbdVS-xIu)+_Nq`X5}R& z|4j@7z86)E6g1~Ca0OMZkMDxzUZhOUiCp%*pRdCZh zeGc(B?&M@Trq!gqm9BxJ-t<^RFDO-03ChwPfMcv zSsnU?a4_)ynY1@1EoP|>6MK9WDe0+I@Y_1hGmdtQQlN3Q?cO|Dv(SJG6 z^Xvdy$Zo`M)J_^wKO&muPuSEQbe0{ygeHNKh^7q<&;K~P6Y|CQ`Q3+VhhJElv}>l} zamlR7@da&mcnA&LxW*@R#J||%(h2u?`q(GbRS~hU%So*Z{bJw|Kj4LNmdro)#iS^W zs9kmeNyfAWlF6e@IBCM9>PYn-BnW=P+gtN zjsCstr$)nX(KH(RATD2auua`nO!{WnQ2p0xsZ{%D86pUfl zB~ghL(NP+|N&W1cOAA8#jo=pI(LbzW)yGsyX8bu zG;vw}V{h2EAbEgBi!{X!!k%)h+Lbg^qZg>&Gkn5o+S=~_2u>1FAhW7+IHG?@O&ypb zkdexSROVvxr3F$o7m`Bs2a+3G*^Pw>iq4@kzP%#*q;Rx)wT|*{rcdZNc4ezSx%P$c z1amGkY1wZR2`BD5>TuVx_#BNS29#?C0hb|7`5g|F_?JT4g%wi zS0?O2yQRHLJwUg@bPJ&$_~9>_?Hhw?>wW8ITUYqS4Q3S;cxMM@PwmQ;c9%f>g3a@u zaki>Qy_Gv7&HNYR{7;lfh5>BcuU~#zJ%1;3ith*AdxXrQnef4@K=Th`3u7qrS?t$F zxqlnQzNZ;<{NJivp431PwT5~fA(S33V$K-q6A}p`hPkl<8^nqt@ zua@ECT_};)j}thETYl(E~aN0oWp0{N+ zpM!SZXBYK3b2?R-``7#l?Q60}DSg_@Dx8)Gk+m?P0w$Tt1gUSzKG0PW1bs;XeF^Uj z3C@)tuUcwUXAWckJmy2{084(c`$=hlSK!t35B8(gse$YFHfeG(ZT50#kzyh;KY=DN zrP3?1Bt>yt|DWr&h4$1qR)5G^JP8&0E8i<47~Ki`Z2CY~Oq{+Qp!*Hm)6T9nY|@+` zHSjNXx9 zM8bPA8ab4-3;sM120WKMp;2rck8_;y(RAV!@jW8JDRrF^S>|>-Jtj4Y-xAOq{;P3* zh7F$939M{r^E|NvJ-O=gpe?zEIh%N#isEDggJ(++4}l%wVotbvDK#Lh78&5=kL@_BKgSWXpG$L^w=wAuU+eZ7B=uP)kv(C9A~{6&6M-gX$61|-bw-F=PuXhfxKnF}9F{+lwRX4OMZ3TLB>;UsX??Sc z#}!_u*G8ku**9R_3_-u}sJwaC54uf7iqozQX?OQ*5_{p}Lr1@NwzA~TbyVfDFflRV z{Vjk7{PFrLKX!Vj>Fn^5mj1W!9B9sPe7dCKSTGER&obT0a$w`#tpiXOzH#>m>O)$a z##cP@h^_4<_T#-dbu+g_StVZz_a^R&aR!-dI3TzqDGwAQ8mwWAOui;zC{2s9^-24A2{ zpZFblDzL?yD?kdZA;*DfIcOoUXH(i_{(?Pw`5o-pO`1w`u?AVbiq*`qw$`J|7)ycK z(FeieRMg*6+8?iSkeQZlvu`6E3;+{%T#9A zwN#$xN7pR(YPLtZD*jvt)TYiE6Hv1gsKOrj%df27Gx7h*4<^8rZ{F~`Wt&l!#ldN1 zE6&wiG(T1T-B879zsAmWJ_dpgp9QuQ^kZ?B-r1{;`^VX@+~eQCQ2ldiq}}tP#f4b3 z$5co(mbIj;{Jmqu&nN5+Ipr#~ARx)aN&M5OmxvqtaWiw2zcG}E41b~i$!(@iNV8P- z_SL8{k1$Z@o^qji6$D=|J+lj&=*qQyIlHMPLMr43|(` zF=AU`u??2*eitzq)*AvN;f7DEZ)MXG2pZK|g$bt)SoaN3QooY8barN33OxVr79c7Q zJ~`^^&YoBBlAy~P{AlH07w<@3-y^sida(Q~Az60KukQv}0nZ<{sgXzWG&BTIn?>idRMOj$xOCt*EP| zphomLYRjslZ9dVc0&?;l&<^b_Xs?CuTfN7IlJnzXe>WIj&0a~JZ_j0*zkU9*|8GHD z_CP2@d=k2FlpX$m#k;Kjo)XJ~YO(g;*B{F2^=W^QN2>f6E?`U(TqJp}&E`%z&k4V; znt$kiI1b)@=X-!rSpUSqWz{;<2=7aJM)0MJk0OotjGlBlT~%2tNs~n3@FGFh@}n2sUArmS&KBlE%e1KG=?EKU-T~IvP)mnhM?a~`p9>J3uStMrTVgz*~jZ{I%)NKe*#)k8N=O>VZc#|S_9@8E? z!1X^OXh#UTBEhqciy@+sL+@(q7WBe!`s#c;H3SVb_b6Ia8b;@CUOW$Y9oL;O}ujD_N#n6Q<%jy zgzP|ri+(wzuVXKtBcP132PAQh#6=PjAV!yS_gnLy{f*F><-mjfsB%_k-ui<3N;{D(X)lI%+4fxJa_1fhDVEpg zW{1DS*v&?k2mUH=RQ_hj3i#4eDPc}h2R06VY5#7Z1ZXLf&QBX}e{1sy!s!?ayvmec z$5cL7%*8%gcLzZ!-4wbVuo`BCbJVPOf>Ha(6@A;AT-9-Fw$Y~;;y348Ai748GN&1|W2T6^!8G46 z>F+Tz9;_dS-HJf;2+4(?0%iTWXmY|BJ#$7X>pOwh`u#sh?^D<&afU%>u{BeTIM>fx z*~|#f2*e`MIIqVXvrB4I2=TG0>9>l`I|gA#xQ&aK-zv-lH}ik-yL1_tULUGAr!k*H zHq0*s%F_rd={=Okv>Z9DFbtljqA-L$2Z$3Z`ZKPMmjXcOXxma58X%9O1P(e*rvc}r z5r<*c43p?GLMW5b04fq5mM$#)OKKEbv1ldy^oAQ5fAsFFIcGNjK4)^nZ;O3wgG{6Zc*O={4cItCRnykI!(y+LL=(?9{BW@&-f*DU`UH{JX_hI}% zx=vurtnviA(AhY4@7u;hwz0~+m93zP_$pVbuSb|6)FJTyG&L^DB3gs~9Mt|h%vYaG z`#>eDWd+M5_BwOn&uu~RewdDNRwW~RcWHjB$n)HhZrAZ z?S`{a>i2O}iKu9YZr%TktNI!`^k~w`0#}vx-`gG&kJHnqOKvUH6`jzRD#Q1N+um|v z>tU~thp`Y%^RvZzQa385h3ofc==9g@Wqas8@*QK#S*fZum8!+b3K1gxFr9WgThk4g zt8XKSepzHPaz+p8!lXf_nDh&7AqkxkO*rr?z6=MgXi7D$Ivvmpc5E)vTpq~53}k69 zzbZgVmr}ZZz6pA-P~9F1j}5a^eb!^w*3ojCW&Y1mE`(9;-^p74dfNXE^tyi&S(muM z3vuZKTk3t9M53^VZL5#0`1%HxfdoiJtGFPX6@c5r&u@MLWo)KRtmHsivnsVQ0AMWK zXwT~IUwf}v`Xa3pM4NIJ5FJhMCLaR8R8@Uj)^M|<_OL~`ms<*(Z3J;?f zY7%k+oXV(s-5A7xL*cuQ@G=(A!E1jHXc6!$DhDc97$@9HGCugdufZpD_x!#qWz0qy zOQi`&<;C2rxF< zI1)V1^Yx1?Q~!VOC(dU0Iu23?Xewu&j8OS$R7SKmB1qJo2EK&|d6dviaCh| z>;4Q#r}!xYWEu&Y7NPPGh@p`>rs14YU@)xWYveuk$ts#Vz{K*}a-9mz zp?!~KYT>PhoO{IJbfDk^5Vf~0cQ%E)Ht!V26RS4+LirOb7VV-hQ_+n}qz#}4dM!UA51DEyU|r`4 zp%Z$rwIWrHq2DyH>hht(LngddSItcN+;h!ioJ3!_iM|P&g0g}yHlGT50wEe32gwTNJiW8s^EUKN=iw&KFkSoc6kJroNdpT<%it z5@D*&tD|niTfe~3JuMsUwPT!D?+HCMO^c*`{^Upok8A~fdpAKtjY&6hZ9V&s#wzHU zF89)^a8^6V8=@0f)VbLwY#Arf794X3QP+P7k3&+GA|D@#Xy}EO3NovrTK=tGpUf+}Ul|dIX7@X*TmrLs2b1zB zU!YJb2QNt-@?<(6f*uy;%|l;w=;5>v-vfFt8$?Z?^>>#)@u!%6!&?*hlpqhiWDokr zGP!WcBsC3lI-Tb?Vx2}Dxg%1H5MBADWhGGS{`fER{O7CuEfZKE#_{uBLi(xP{{Pt1>@LOU?8s#l%fxH3l$dDQrDC?SOHCA8 z9Y8MBJ8LFkgDOa_Y4?4FbFGwz^CQwD5$UlrDbY&{-|l^-HE&9i*6?)nts}m#isC5} zGJav|e@s_+Y|(`4Suo;%>ge5V&>M%KG?GMvohaI%YDIE0ygEMCV4dypkQujx-ZXj$*fKN+G8FFm87fSEtH~<9k)$}9h zYqzLNBa7P1)`Wx`?1NMXDxj-1Q{G^2hPewH_o_p-KfzXC%daj5+8W@;;^A~Zi0xD8 z&jo*E%tADso^U>Gaqq*eatYH8p;v*{aH!S!cl1qsy%&EAmDuO@!wdYI#Cu?pDSHw1L{c!x03H6Bi))iNUp^hs&gUj8^A!&{KBg-+ z&;E}@ZMs))A>_Z63D@B8-CS*^T7HDg^C}~m>Ib>#y0*QQLGs@Z!t?5hnC?(Yv7lLO zP0itBgxlaj|3*Sffi{++HXSBUeN%r0nn=syb&y2`>9-sW9_~u&} zAv4z+4~-ZG>R)HH@SMI2UEOZZ z`B$FjJ>qJDnew$Jaeh1H!_PbOQV~gw(Q==I{;2vN9EL`HY*LUm#*#SE9R>N-``--v zSnq)2i^kX)^EL3`32LKQllQSjYMd*mXt$+Veh#6R@xTi6l?*4a`m0#h&lqHukiRe{ zU!-j(ZYe4NlK68?v5RU`pu)0M0E>d=qF3)2KtHm|j+9@tTbzXYMQpfJDPI#0Lr~$v zk2G?rb~m3Sx`LU3MWL8f$0GPfJ{BHVn!oB9=$-SARv=kll~!biEZ1><5& zT)BL=J0Z7{wR1R>O!ZE(y#tV=SQS8?v5g`VXVrNuA0tWiXyIr(%iP9CqsWv&+M=NJ z{AGLV=L(62&{fi8F+*b_LBo)BTos$t9Iy8*MzvV~=5-k~!kXR^^rG$Bw~JLEiQs?e zR=YoUdSMowH6LZfcYSOrn~??^+nm6expBYJnG_#{ePvN5mZQv-gE@y@2N?WbYNb(t zbT9XP`9tLrhcHp0N z>6jj_Mo*XSt(;e?L*AHtu6SGpZ|BApNzMh)oh6$9}F`-16kM| zYLli2u`awzC}*_sq5E(6$iN+(1!Btzj47^?JA6qC>t$Yjn1_tZPzmV9hijAF4RM(s}aXR8siiAZ+}TTx}rVc z$9nRv@00h{yJD2pKVz6u{c3Z{!+W7MHm#lEA=--0#X>!-l(BEftW?V*#)@xfDV}8G z)2O=~)-*1ZWx)>P9xWqp_^Fg$r>&%oDSvmVy88J967;h$5ERWh=)xUWqE!1KXmuNi z;JBc{lC~QeDFx~YDyAYbURq+dt*?9ZDj0=MG08hUB0bQ5vKEUNP=8Srd_;BlUJ4UR zR^EP9J`pY3bTYrdj|$A*eM?v_SfB0naZH91$TDm&^?+5jbsXBdU+sF5EiYAT!A>BT za4<2>#?RO?RF{4oe|HR`TSvlSHAX785iDQuk{v4--_FH^m9x`UY5Ex}qqFUBzCV_#XItBl&p6yT-Hb(0xf8#(dc)FygILde=7ovyphLq)rCiz}0;PAw zLYD-J8QX)Pai?HXN`l|x;P+1_2Wb{T-d2TZ>(3$}%;ztOU(wAimM&#qGVbN{#X6j8 z{S#v-+yyG4w+WGGPiP-M=|*agqz7s%@W>~YZ~#3o zm*xIF_W%AnhR(1C1eBE&Z`K^I-S(_cQ0mdMPomU6c!<%29+b6T)KB<}SA0I0R`Ne> zZc=mUB?q&JIWvdb*3?d1aeI+Jrss4*-FGR0391tUwA2_Hb<+xc7Q|#y`905+v4VJr z?i&~$O^@_l!zjwB4bcG_WlT%=TfkiPk^ZGYYQ*>MD1$6p&soeE7O_Hk)Pnvar#da4 zCjq!!WBgcSH@M_&7)(sI^$*zp5R2Dm>qh1cn?rbRRL{H=$bfEQ!tROr3ObfF#1)}< z%s}SR9_LV~;uHW-ep$I43&) zL#A~{K|Tlk8ykAxr1oUm)aiZk6K}rvQKdw}X<$)$4v)yf4_DA7UCHf@B)gQqcWa0o zM_iW8dQ~9Z1PZ?SkQpSzD6@gcGsWrMH6_K>rs*hcKaHh$Wv!SeeBZMPKP*K@P|+Y_ zkfG~z$rBgj6+(K9-y`&7MP-b~BDtN&y6ICSKttngc3>BuS@`*F|NT|jVO~#DG~y7} zM98D(Y_}{MiiOys$}pXf3BQtjr$T{oEaO=AQ7u1Z@`K=D-ac$v`OPslc<_j!2WG2^VfSOloXNb1GIo*VkJ}LJ6-l#IUJF{7IL^65@oj|Ez{5C?Od-i%LUI;9 z)0u%6xMg*t<#Swq{J`{wh*EvUzhzC4aAy)EnIlUsZFYE-%Ctti1^z!_=lgORcbJ(3 zzqEnT$ETvKqsYZdL6B1G~Dzoi7oDf;k|9t?@S?Fn`%`6?M+i4Jdz= zu0A@xT#+j?-nMFwBH=Cu>)}>&D)GT80zW+4!!ZWt%x?j>y`{(efk!>um7~7Mzw)SV zB9Syl1(fZ=*oP)UcrGoQLj_rQ@Z6B<1Dgbz!KN2&vbCD#)CK60xMbl~=1*wH13h1` z6xSJ`J$lFBtCrO0taenI0@8dLZXyS_=t=p)wkEW6{ZwjSK_ZHA@F)A9z z>RD{6_?!Lv4@o(d%Y#)?2}*7!N#}(^#{lts!T;^^$$b6Hw<7R`4D_byRECK!lY(zo z`$Hc00F`F%$F`lCzhJtwsQ}qt$@J>K_F|jlMPB2x!`|)no-dm zqlkd&xU8oIVwRW$4R!>BdVn4sjN004M?|q z$mKY?dQQy?M;eTdn?%CHRUJTQkVMgRBc+ItIVpCy>^;2TPAm|B%*6CQ$H1L~#4)fB zB?XfdxR{2kG{Gdruhe`C7h_+g9vg|xM-tvK@VP}!JE?y(E%);G<>mIpFQerXT>gEd z=Sz3UVBw`W+M$f5NYLr?#EQP#$11{}$vm++Dz`VHbpd&4QxE`qPCfvB#-1fT2flzf z+AvgskogqZ$beNH=}CFV72RegL^HjGmppwM{YkCjy4>%{o!nL3%6_&3Kdb+0ej_&L zp2)!clB&BbHI`lN>$$uO{?VVke~(R#ES_|+vvnC@wx_qd47i>Rvv(N~rS=mwL}Bcp zrJ>3Xna*R}lB|J8K~Fjqgt(5zg1qPI_~a?VLQ0UXv01L~7ychslqf6~7I^t5Yo>Dk zHic|oEn9@20MHuyzjUO{>E!LP3^(<%Kpe;$|$k84U29r2DQN?tvzy~z3a2#P ze6g-GD7$R5P@byqVa>4q@!~doJSqMqcjXuQk5Rz*TTr;KKp_# zI;8%4u_^Et*q%?P2Yns^=((m`6D27xZQJ9Ld6)wd+~b5L&FT8}t~Di+o2GDte$*7+ zw$<^(_BJQzw$5^TKd^A!QyR>G3k|PG(OOTIb7nhh&SyMltRlzFk%(O`vR?r73?^8ah?GR1O z?llf@?clVLheAK9!7wmo*I*1u`^x>)Wt*{Qkl2+Iv1sOgcC`Dy`7){DnrM#ito5y4 zCd#MS_=F3sOGk^gxyPNe&mYmbfqb*0QTns!Q%g78?-^{40X+!8`uOt4#jmj}`s7xA z`lp{8^V+XY&MYJ@zTfpc!pOeZPOTp)L)3b{*dCnsa2^3fRC&IzvzasRSabgLxMXbo zB&)PpO?mz98^ihC%CpK?!Fv*AM9=i}6;G_JR?t%Qdh{9g*8M0pQIPU-z!n_)24=U( zZTE#a;aO}xZhs#J%;3*D%xk1$MJ&6@yeWVrhe5*E=8XVyQVWt{$1G$<^jnNeLxetI zH2?iTWmKSAI2mKR|7NSj`PP??b?>?klKY|om%1ybd@TVEea6_Z95<(Xwny)$4)P^q-?Q?mq; z@;jqQcV_E0OTj$x=vVJ8+u#iJxk?>=j~V!w!g?Cf8LXST*KG0mN$2{&U$)}R-ua60 zl?~lRp2afTmh}cLb1|C+R?@T1@QZsgehfdJl6pD3AGEtYo~lkLmvB2YswY)|$8FgK zL@Hn_!Kr28@d)C9YaREyuQr-}X@3fC7ZLmAkp%3(#T5l8E*3V%@=hr~)s`pb5MUDN z#+RJwng4ci7SxeBje?P5buc~&Fp?`)aX$_=#Fx@yYrKCZT%$Wh!}NTu8d9FASOdes z=e72Ydma7@sG!Xrb^f^qFLEO|VA_q#X;Eo^#{O@4y2_BgW@_WSXMIXkwG{dhkI9=oQw>%3BGe!{{{*a_nX)2KiHt?G9BT%}MlPqOuDZF#qLXOu`A~l<0oDgDbWP|k$GB0zw1NXHM`w+?@RF70H8`X&yI7!wFVjk zpp;ET!}xK|3SBOgNpX6g| zz86P3JJ`y_&&Lro7D0cy%xgqV)b&lC5gYVOD^5c}Yt4YH3&ZS^rxP0_wJ1@U_{WSr z59bj|v${J?P#ecGELOs(j3-}Qrkdjp4LdG`h>ObA@SI6dO)i1oW~@?!oI=?2aCy{B+kC&QdMCxqE$5I zNLR3e9?YR3%Lvh#<SwLSu3pZSGlHj>jOC70ZMi<%~y`AAx&-1ebAXi zF5xC4S`HM22AB7Of!9U4M&|GaVz3T9d))pyexqi#<=A0U6i~=N7!KNOhzTWh)7aZi zuE9U+FTSU&k z$2PE}8z2<(ExYf+>=cKN!U%yeSAuVe{tffMJMORHmf@=Zz6H`Tia#dOvzE+eWkqqG(Yj&Ir4fN z=bZi&k6#I6GE~=+5bj#LjUng?x(}|2x&5aohMViwgO;I-g?s!6A|g4WZzk)VoVMCG zz!K57wnks3bLXK=n1@rpgz)lZDAx-oIr-o%+(Ay(R>WC{liPO^Zmk^jr5=Pe`8V7w zeOwZ(a+>@oPrzD5U0~JW(@;S zw9s2F9;=A%VY!xw+lS<F|#b4+v2ELQ0*mkoDSVW?cenn^9u=v*oepeYyONjtV79B=DTA34R~3_~T*V2v;B7O=g1x zGJRF1p`DERwmKrFYI39$fp8ux^xzz~a=a(wx3Wve-|>9J*(q$5i$QlWvecLj3`~`I zmQc*3w<2zILQGI>EBn}Y%Rlznz5OIG@+dXPGj{%3`a8hlB1!)V-S_(zfea32brVP0 z6yyB!KMM3j4h1tn${9tAT8VKKsiI?89hN$unZ>kj>GYjDq^jsok=`HIZbnr9NiH<3 zn+6T?HWm=Tr*kxX9i&gMr4^_a-vR-4h@8f+^oQpHubY1qK$bSF{vv7pQr2D@y1SEa zWf#C!Gc>EUbUtBYR&3QEkBDs9s0ox;SXgMM(r8yp0DbjK&Wbb#{%^NtYvt`Tkt;k> zUo&t4jQ(3LaQj)ZP&gmAm&F2F>)X}%MAS=E&_#Vo`wj4lIz)4S=T@IuN(8AwV>skp z77r|Bz&NlmAsZcxG)QbqpMG{1K`8<5;;|j&mOJ18p;I@BL|PMkSEJ(UPM2 zmOjO>e6izzli>7t@OB~kVOZKob^RB`(76oYuihc;CS}#S*C1PN3u4?Kg6pf2BW_bu z5tcN0epF00`Ao#*TUxxsE9v|)X!mG|&gh%ZublRC6e?fLW;>;c(B(V4%V7FB@f|c{ zO|o>tBM5<9Ua!8OO)C&oZYjhIBhJSdFLZ!f=`%CW@~hZ3O3a*tSp!fqES2 zr~r)_Ttkr~XOJ2gG-nJ9h;&3^eK9aiM?T>2cG`c$b2NHJ#^FjMP)0YTsjbw(R|~`V z1a7YQd&55O9}QHt)quX9oVJ!ClE5vI%9P?lnc7134gi{}F?U_*KmP+tQ}ZC-1dy$h zPYb(e->_l!PkWavVeZK&GSmzsCE#+s25&Pe4gGMkb@c79BCnq~M*eKtsw3~f%gtlm z=)V)#Pr2WPxS8k@DIM%8gH?1{bnIyr-DfmYpO%`f=f2F6ZSw`CSR5P)aiRrcljKvN z)ek$mrlo2$ovjDB`ClpMhI)2?cxh?|#t_D<`Wx=YVpuS^j-(4a)_(JbXQmmBkpWp2 zOiu{;<740;nEQK}(!6{({}4zGw7~cDujN&qs_vOkjnc)RiBp zJ@<-CECZXns<5;e$0s7DCl5P+sKXEeSL#~)p93%c&XgYE)z^_3RbQ_vyf@8CS-8u< z&~d1(k(7?VzgN&>?Cv|p9vBfnLPK43I=+qmsbYwwYEK!bM=O8%KH z8BM~;iMTC}YEEQG0u4WP67XpDHtlhdr~Vkl`X$5CcJ0aY(D#1xhxyCI{T%EZAKd91 zNN}O;Lb@eRRAw!=y8V6M$d`#~@X|o#GnLqP8xWazKqFO{uj%E%nKarfc=NowLUlq7 z7DD@lafU1_8-0OR!U@=13agoG4y1VEagHKSLqh*UCLOR`zmBS6y&RdiBDI3%JWsoN zqUt<~Fy|OYid5;T1YoL`d=oY#?{w<0V@2+_HKw}b!R`AOi+$A%;Lh5bZNK4@ZUfYM zt+=^w=n>o=nGadD{E-{XL)77?K&TD zFoZ_RJMPrB^nEoQALK(|U2W_K1_t-{k5gc8={grjcH>lX8UTLiDq;X~nMeOC_l;M3 z-x%=Dj-dXoLLV9NsG*w#j<(YL^Z!zh>k zN2`uKLfP(4yEY%IFC{|f`U&Jb=d`&x+?-kiBCtq)DRKn3U#W~#xLd`1niHs>IOI;_ zj^lO5;P=Lnu1MqFVsj$Z$q=&8$8j{s!2o?*7NOeI`i=|lt39jb1E=lmUtiKv$0R;h zF_qS$Sz8(pR9uceNaaxmqt{9cYOeksd}Vxndel8r(SE7CWWLn|DvYI~Fj=@CrOGQQ z1-!DVynUo9xc9HA_V%R!8-Jym1nbI$O(<}j2cz3U$UcF$C9o{qK@?YX*T*qw@5!uW zB;3hiWP%(g(6cV~SNF@3OWV8k{8EBR`Mftp$W;Ov7I9gX*#3vG$kNxYSFMbO`~iDC z^}k%q9-;0iuVr?YY0Gv$%XPv+x`i|hnq&Xqs?J`44ygmUlYLguk|0E9*1@-v^AotH zWBlzHT&duCzs%Ao?6K$=Tpz=2%j7~`c$(RxnfY5*<tFB?LrhnQU5`nRbIBXe2K?TbJOE+`0ocnvp-}7%R(E1W)i0cF48Ig-n zTI(w*aQdYDx#vcOED#Eje5~y8VuLamy6|O~?>q)fjw|<(%GF~!{4K{>g&=93rM`w_$d4k`etU(8uPh3rfoWBQUB+$!iR^!M|Z`SSN|6)P|3$=y}VLHV-+hf)+`(Ru%1 z=%O((VSP$+^cQR~H1 zGxc0JR-k}!{#E^2FefbbeliGO#I(1}TZf-BS06QA4u4ZsfDG-See+mf9zKpW(hiMc zpO<*wuDa?prLRzI?F`t}-D@@1<^R-NEv5ENR7S;i-3IuyL!?H1BM)2-Ir|YB7bG3? zKrR64ZDwp+`fH!VRuF->Kgv>w#=CU`@veOIaLC#4Ex^`u{sSN2%1fkwidI44PlI_K zCp-NoI@tr`8q;4P*N@e)7c1j^szAb7oJQDeAdGXiW3mxxJZ22Kx*yFS^sk5AVz=9% zh)s{89#CpaGny#d6o9l7cgj?M+1ayats)eTL^j(xA?QjrbpOC(1HG0?@88QHm0!jD z!=#2~(9CsxOR+&Ze!byGVAgE=ASOlT@;(t+!Qqqk?%m`&J!Fif; z-!gn2-{duu2OhnQI^;qm87SeR8ADT#-%DJ4rPhxiOCkWtQ0fXuvMkxwzljt6m0%R` zk)70tb0N*ALKFb^w0i)wovr!fMokwwd|k(|W$GP*FPOfLqdSESp@_lutrfLV`2X(t zGP3Zxo(ZWoT88iJA`3m`ApjUXg5>|E(nE*WFZkRcjS!t3tv0bd6tXqLkEPb_Og&nf ziGGRjIc{ja9Dbi{UgJXSmMB#2=3ODu<;04UBMaObYrcU1qi^DQ&O`es@2 zidI)w)GT@7AxIP#^I+XjAdeSTOl`)y-i5;|T30|x7i30@5V=dQ_OaaX|0C-y{G#f< z_F+UsrG_qXU?>6U5D*50p+P#OBqgMKKv0PpLP|QNI|M27>C_w#+? z_j&&UhI95=d#!6-q2BR;RVh~H_-(s$giW(VN1v#Rud6$g)Amj~+0vQaHm6{Lm$ zJZga}=WTm@E-+gxhF`@21Sr!D++)|OEYgsTTkM_icmd)nS!ag--C;ggb6NwS@Z+#VQ}cYCzW}{b6kt8^_JT++s40~53gNtUKsPQaAaqzO9U@xZG5;S_V*rjfS&iFOo3JR#ZK!^ zV!g`a5!KK`6a5H~fu>(8R_rzcxUO|{hs3qgJuI|*TOuT=!dK$KEc`qRGOb5n|JL4x zZ?*!)O`$KfD+8;kEaM@);q&08++<_^#NgYc2BX^2lz5hcVsGAf9?$DJ&Rw*JSG4+= z^MF^3V{g7oUbD}xaitPO4{aeJe7Le*kay_(-GSRgjKyrf>Pg&fkjbjQ%0d~}F@Ci+ zKbsEhYHO`~?txh}f9uJHIhsE;JUicfOA~}^bKix>iRj2d9@=Np6cq)17461Rl)C13 zK_Fap=z3&qU4q36_lea6WO=)RZ9Q6y&Aa0Xi?!u=LKpn$7u}i8O35woF@Jc~8 zN7t{Mn(5>#`DGfbP84*F+FV_M3U>sT%f7t`G0PpZ-3} z_yUOi{dj{IcBF_U`V-1PQlE;D^fjNoG>8qrPBpoWkYPO7`>qM1L3PretyJ|mPIdmO zGD~lN8%nE??X}NeRzR>IHK5i4PP%Zao`ly`hy4vDYTQvE#U3!C8pod7m6fS@DbrdC zI@QgyvXRjAwRsxJuy{6^ML+=s6fY>mipn2I%P&r%z=)t#7$;PmQx%&yA`O02O%}ox z;64t-hn1VZ*0b-jnaEK+|FnP8kC(2tZpy6euF~z&9T{E{T@=cJ7aiYp|6RS0J+|J^n$KY2iTWV^SDq<3$~Rd2gK;GWw!B0FxL=0OqC52 zw4Cf?mn;Y{mMHbLiJ$?wv;kB_`3#rQu7N|JP{>HvqC?&-3K#HOd%$$xdYE`UtfW4b z5}Ng635#8|FV;HZUPw~zfL}PQ`*r_E7&k~yv=G+-dfvaIF_JEX8`Kd%9%|bY*MQ5o ztcx3yfIuvS!*c`G+pn)qzX_|+n>3$-ZF^l4u;$qZ8wpFnyN1=9Ezt#jO4_gF3{U47 zN2x`M&n6H~$hER`(kMD9F)KoCZ17H*a}%K3Gf+KMfE3ZZf{42~S-Y#=0|=T#OlNO@ zlD}IDh+F=Qiao5%mB$~|?i3w%)>th36pg2&6>1DV#De4bk(1Wb!{Jn(=40A+oiBzM zpC9bTH$X%Vk;=*U#lm_zQeSHRtwGiKsW*Lz_4_!-Ujo@9`%;@zQq7JO}xsPABQ0U!G8 zJnH_;OJ*MV=AfS;%AgX7>d39b2(R<2q>=Y*l05r=5|ELEv9%5@Y`|E}&<#&sZ8=>{ z4~#XQ@{qzy)lZ^N;*y@`x;*`QtK?U3iH*9}-i3#WU$P9~VOtBjbwGy+G{x=JT0Jjj zKa@E6A?5!c%qTl004Lk;x=Q+t+~kBEa7Qtl;^x_?m`FXvdDP}ANSD8t2R<1ON0;6b z+5*&isv?F9o@cr^5M8D zycigA_rHjHH)nQV`qv$b7Z!HFR`Cr(xw(zx&8v$kbvYy?=a~48ufWNE8J*|SIt|Q| z?hu3d;a4q)6|;zd=Z*#4D1A2@l;3Mq<)AiXP@p>ldFyvZ5YI4veBJX1Kf=aRhk%a} z98a(jeC32j|fk?|Nn-|WxLUKolqFeoW2ZaAS2JQbcM$f1thhSk=?RW^BXTa3k z08G8+8>Zd^7YbkSyU^%Tnj2;uOC*3`4;Eyv{FRFjhZWwO6aMDy&JW--&q9GC_A$_j zndb+TV9hMnmvs%cHU2a=dmUB>VhW$c7E*#6mm@7tAuvY8a^=3Xdp_zB*^6F{by$X+ z)}Y;fGs{Ztr+iW&pX9pR5O!%A8S`#~jKAw$7bh)OW=Gv(JjT9VwIP3OT_~#i0P#vm z++J=bkh8L9NJp0?leFdNY!?B5=)+8lAJF_|c?FYxS~aj@c5nJJgt;!)e*$17&dF6H zGDU6bJ0L`I@>RasTU#Zzh9Pj#KHwilEWRy&SzgdSniV?1t*S1Zx+kKA0?JOB-1NG|1kh#@i<)w~sB;BBclcJQAhj zP#Noj&(Fd2e#5(^C;Obm=(PSX(a9%Eh(MUZC1dR?MG!qwaMpL7hU3F9HT?Cj1fu;!rVgm) zFtgFEy*2OE2eFCqIGJFF))Ktk^o+o~yht6K@H)>|u6LL<9{d2TC;Pb`Rm&$bt!HkM zUY*xqnVP5XX0Up|(^Z2b6d#QnaVzv4H6ODCpAU^p3Zd*586c4C2k^a#??p!sPpy!F zeG$M&XB+?+IaHQqaVzBWViZGWw&@L$K_EH0_~BLDzb47uSAY4j$kyhk zR%r6K;@D!D?eJye=+!(4NTi3c8=cVp2ToU9CddhbwJb#`2)=hM*VLWDB2GKbPx)gpL-QYa1_>@omBF@1qNdJl3M9_j-%aC^yLCz z>*K0nw~oMpcAq-23?V)tk``$+p)CeJYzuZY3rN!BM2DU!XQX%416TdDL3_56iDP&3I87=l+s(FgYsaI938yRet)J` zq0l7*x_vg>JfISIX(f*=9K6ddatR`al=?xGQSe=_?jw#+zyxa3@6QBqWU+dC8+G>7 ztqSo+$Q_TlbSTk~*+Uc*n>N3`N=}V%Tp~xQh}d+B-?sRjNb9tfnSvjq(+yA9fc2~U z%J#uov;5B0Y>tUh-c749WhvZicb>leSD$ofLD?a%Xt&h*NpCHA|>Zo9`-isOd_XtC{{r1zTDw-yy z-qnRI6W=r!PGw!;WlN7UNpItSPuc~_G|smo0JCjzADuu9r=xl({LxERI)OOwQ9t(y zlm-Ey(*=bZ^VrKt8$>blgy_pLR)=v#0v4_)xg1iTebA`?(523OLf4Zw){cV@5G@oL zeV>_2M-<;sH6OFPT zbwBF@geE^KW?>4PVs*bt#CRf7LhFujbgK--xT%n%>H+9NKXJ4+XZA zUZ7%4G#G|aMVCglCy{t9CJ}@+a0Dg^>k?`D00Ipw1?Yqix<}*|5Z$4R)hybju z8wAiJPE2mJnhJP2Kg2!$XwP_%042geZm*kFhZE%tBgB{9_n*-S^Y*hck7-*fm`A*o z@rkH&S8U=(w|5hIAtjvD0{$M(*7P{}t?YL^uHF7a0pYSn% zffCJ8LF^EMl)xMl_Z`U_XmWn0(M$65cOVSj%XZ1DBmR0^1%^MYZ8^aXlns2A&mr%K z%s84xG^~EIHu>-u49+&3G|E?Q=vC3W#9k8POHs37C1iS2s8e37vtNh zAR()B3f$D@GkBWslAQqP^e7ry-lu2+Rz=0Fw`8Um*UY|Ax$*%TDsldSZITf0i&t#s zaw4y5LVIEMG}$1`8KOpH4)Iwxt zZOb5ar;etr%9p@O37l-9lcu(uMceU)K@7sEQyTIdug~g*3-002!=F5P(tP*~u`RS9 zcoCPKVK;hu_@kl=xTzG(X6^qTjtA~cKjCS1Tl}tETLvp%uvH~NH+jc&Q0@2VTGNU8 z7Q6n=hqv9NW)!3VQwqjQ-tUSmK7`mN%*ln*>siw73N*tAqc@eMJ19|eFahqfY7G=J zzbEckH~T?oVb3k#S=G?E*V{${YwOt@CH1l z%x2g1;xdNvs%hE~UztniHqIq2@^cRX#EaZ|aTR^fToldNo5vLC3_hY;d)RPok1NV& zH;0!+q}<0`h6Tfo2n+J!!Ben|v*V;b2D1=lNA)G<^WK+m7Rvvlf%7SB(Ker~y`7pDc%KHgkRnBDjv{W3OQ|5ekQ#Bm z*@U@;e%2?YVlx>6X+XV!UE>#^JaGD!WeG>qgIpSF!n}AxS)WzOEpa7U~e zQ^yz^vG~xmV+J5|jTdFWYOcj?tHb`kt>&gfOZ3qt5tDoO+9zClWA+n;i1Dtj&j!u5 zXJwBsS8E+?Q(HGxAte6Pw!2+5T8Ej#K)FoUU1&IPLtSlCssW-nz~KNQTQkqlUU}0! znsu|j;(Bqld`%OhDMyjnyOKi_obq~5AiikmjU+sL+3p?IyA)li;`kZW-bZNLK(KJV zS}@@FX8CwVt?6_n^-&)Sx_cncZdS#@YxcfA=qGas z(xHrLf2Ls_mfWPW&YSJUwvau6z>8MPI^##*&CGmr`ukVG%J*Nwj?>g6dPM{}mdEWn zHY!FPFN$lOcJH7VpWIpEsgRdoasF-%`f~GjVx+#xX_h|ki=rv^5*-s=|CI_U)>E4^ zy}o~c*OMOd8?MG5(G%>Lv>R6Q%G_mYJODI2b#$@UvkVk!yl+wVI+efv^1Qv=U%>?r zEJU7Vi_2Md*;t=(7~s0JO4;xD9URV%0aMGXw!D(9@;W?&Diol_ffjZ2g<(^jQg!FY zm;uvB6t+-LL3?z1vflH_gFN5Ndc!(-NWhZ3e^Btj_l&cDb}=efe()*ww(1x9`~09< zm)%6nrQE`^3I;E*=BN}FBMSdvqzXBaz!}DHbk>Lug`&#+t^st#nqWOH~=%+~u5;Phq=L|VMN=8;3IrYpg@k+yE zB+B=6-ZL8^@!R2%C0q^jE4pUFLk^Mv;(3n3s+mBp-)r*N@cuuFPAbV2lLkz-dHu0PK`OoNEG7|q`BB#Ue(=nC1oCAN!ug`dG&gX%Z(E&bWdrsgL7aZWTQ$QAe zqY*d!^~R==<$s0YG6_=-apovm;jgRPlFCdPi}f=YGD8(%}cu^GCLBE?EV)v7a>r*sjm_R;1kA(*&O0vd^$Z5^ojK{!Mv>A-+m9K-T@h zOZ`@%{O>oiIitV!$+C_j)!kK7v545~mQ0w|?U)4lhr_OWR;~&vtSy&TcxZU8h!ycG z39$}l39-hru@SL~rrjbn<4Ujuv9!zXc7NtjTnJ~Fh+(c>e-N{&YD}$iMh3&Dj`J(zA0-Iwor>)2r?J@z zIC~Ia5e9`;NkfqOZZ_P$g3+s{EYp$?(`91H!WE06^=`=w>)nnK*^jfuzN7h6c7*_J zemVU-F94GxC2_W8Z@=fbDc>3x>A^a{kh#cG9^3Jz%QV>F)iwW;7I3N5;5k#O+{Uxh=1#FY@HFfkL_@-xqupbY6Jl0Qr`9hYO8wSDvF)aBd z8ibKQEUn+XTf6X$Gih;YBFQi&P80Ufjziu6#zJA5KL(g=x7|ewjcs=PHx94+!kzq% z|149LHr6REvy@AV+6W43^{eEc^~7F9JD$ZgPhb$HDT&Z3c~)>A4JVVuLs#x-jjNv% zA4VVEOfJ-%UJbdiNyX}0MnGZ5UoaLr))LhpHM2w-di@Ed z_ftA}&820n6Wpym4dS=mE_T;_f!OJ&Z=)ih%8sYh*r-DY`}5}IGPCoeV!tqa_@*Q< zufE;@Eck^J4v3_YTSTmLIZ`nXauBTa3H4ZXPz2DOeZtS8koxb+1z*F5b)!2stFvqV zE<0U*Xs(okfawhhEC8^li6KLAU+acOqH6{e#vq~b%=x5Nl>eZMrAd5x7*uP#EYZLm z;l|9%`lt(-d2Rh`<~1deGkIe(Ti9dccmv}K&shrU1xBoJvkkH27_4sf3j`Y=uK5!` z;q7qS|IR>pKrXyu;H$}Kz^Xe*e(dHGJdfI6RotFpPkv9`$^CI*P$L+WzRKL5fT2Px z%J`g;Sls&|oxf85XmKY&#{<1F#7G-DYBHYBnl5>dTkt1Gwak){;aqvrj&>aLMSbUd z+4TAuJ(&Z<< z@F6wM4zk4|1WQxJ+g(gNI(td=Q+r4{J6v1&OpzW85k_a5AGWaz%{whQGMhc}2F?F4 zZuaj{fPGz;zK@I~Qs2dfySf4z7;NRUAbKA0{Rp0ws2;;!d$qIQHb3q<0~tTqah z2~xRyofB`oJy++y?noflw;ogga@l8KsQ!yWuGiG4gB@E#f|%W*?(R;Qxkume>=QYm z#I1K9hA}5|0e=TBlO@THM9M`kdE>pxcrSV9mX_lMsDPiQ({j3Tmls)Q5p!>xa}Zld zraui*O#TabGtH%HXaB|ap&m9yW=ytBallk=Luh~N#-QhU~eKzakr4kB^- zmgP8_*#DlEB4L=C33n{?sU!Iz@lXGvYMi7bcBqnq=r`h{2$aCRJ!&{za0n>2%O&+H z_yJ+SZrHrq!JzRdBs9ST0i00Vjc>d_hmXx7wqgKmj)KT(f6lV28M%js6X8p3(=(88a*1AGal*e5rx zFI=y&;l1}z-=wV}ye{sp#9>KxjgJ){>3vQeIdsOQeL9SpQg-u+W0QI>I>t~|Wn8P$ z%<|J}h16;V@Nr4n6WQ|_55z51lKPw2bocHBi-Ygr>gGFeYWCP$s;aSA;Goce11I~= z6pXqIZI_MegRnZ0n=bzT`*c!lb%HPHZ~9NE8*Nd-VuxxnDaK<&Cp zdEi`e(&j_%^}vp=25QD$K6e5`eF0_X-~zmt<9ZJE%Sr-rY-DGn#vn$sf;{O$JCHf{ zT)q80P4>0S%5)$x;gHkiQLBIF_2t?;F%xnGqcF`ohOtP8l;!w3FgXn=9sOg76%z@* zfgoBmxx8PpABLbyIYu<3s(UyV{h214rQsr7?8M2$8~7cRH`&-%jceMgI5aeF*VT3N z<`}frDdoIl#W}8=x?Gk`={Y#umG@tg&-aAkpT!lzD<0i^3Ca91`)gSjl<|WI1_71# z8C|BLDx-AuxFK~bWEjbNF3J$dt!y#4_@wbn6!g1-PDp9kpAzdoV~XxSh znl;296c-BK5hXlxJ0xLj1mEt5#Tn0XzoV=f1$9Bf7xxe9$Aq?V5kFc4?-d`6LSX$IJ$MW}!+5B{>%fEZ4!KdB#PH zOMpbX-Tv+$F|J5D_Pw1#cZMfty_+XyG7qPJxkpSh?-f; z?4fdzI-`cQ*Pc}vjkiK+7MlAridpA8IxSC|NOI!fcx5MZw%&ia?EfKiML4>|TYsm2 zc3t&sGzn$X1&04D9yq96^o$+Q*s5Iw%Oi(94#5PS?xPuf(zY^r^0?%d#KzC7jzrfy`hv z#D*vr4dUSCR(`Ro9pgIT;LPsj28A`TNo66dwEw10=ac!gfH($?b9j%BxS!Sn$FW(k7U&uWXO|0 zbjpqd77&mIwGIPh5y$o&ce`&u4$iiPrb3hd;`w-F>W_bo=EqHm|HsDK)+>%pQkvKat&51$@@iZdp;oh~*nN*CHXw;L0r+Sj7JzI*hh= z`-H-Cx>p$+DaNn%Q=oX_fh=S7YJ&>^b0|IP&`@1QVx=Pxg{{P%B+jbpst)!S%y8F%%++W7GgYUbm zy8gWeo8TArH*P3G$I)?J1q_$YeF|V58_skGS+~DIo_|2cmU^)^{e#iv0ckSTFR-!5 z|Ck6i$14nOJ|u8U0#S1BhNXQhGHAir*XnaXz>!VXF_S5eSjo#mb9QqmD>HU07lEhh z|99_e%kJrUfm74*pGe>zFuEO9;%*8+Ofxw*9&iO|TRW?MC)cx;gWp}^IoP)7YyfPW zG>d;>wSD$?aHh-3-#L)j*qQpogd5mk+{b}Zg_NR==?s=dZ9esFv!YQMlDgFjJ*>LaJ7g9ZGbn-O(*ePJcUljC@yB_gs63F)nztP~4oImtb~_#gs&it-sa zArbrRK%Rf3dgfNfQ%o-IUw&i>hJ&g?k?kvfV$u$_# zMR}K^+o;WfX7{AU6}dh}HPg1}++&5OF;U3fJ@v$B4Ww19D$wVl`2T%O?k4wlE|l89R#_+O@gSx&N~p9+ zprG^Vo2g@b0v8976=DD5SC0*lHVHP|Bm1U4UD}a`2CK>9*Ni`AstNUtj(MzG7xiuAJ&eJZ;#cyM1=$(pm%f#r?C{Mx(3S$L zI{1X_;h;*zLSM+lMcSV^K>VoJu7^k|=!NULzup{v-STd)VL?AZjN8(n>dQA3zVYM) ztv))*fs^&mgPc3L7s%t^U0++g&#!iVRY$EaCS({~^a9x66<4}}uj7{JzpCA2!1D6E zI7;h$@h3K&YjS+bQ{Ud}V&l5n29{!)*2eie#8%7FddI>Cqw&6z)|IL4;-qEYA!Zs~Q$P|Mgon zHk|))q9%;TT%qNx$GwzeVJcVKC`{CoeyU)g@YL~{7JqFM6x|`_HA(JM+`s%QWLnc zPYy4&w2cdqsBzG^pb&P`{4uS5Pwk7z11=M#-~?g>UVPOm@~7-)9fNo z=exF|TKs?bEr5yb|G<1~`8QbF{jSd`CT(Rr-KHR6O_0Q6;timwUsp51gZz}5ax#~2 z#^n@s0sX887)!W`-VGB?HVgKRzhl-OFVMc}i7HX6XJ`jcQWSktUp@q`xbEacva(l` z7m#6ek{t_|3jP^AgJvCgPj;j1rIhka*X~k+Fz)7&WFh4u&c%qb=A*t5F+Cpt^R1G^ zlmB+Ei()M&J^Y!!>y23A0a7hE)zdVnv_$aNS2aM_?rBCikFvipluugsX(@jA&I)-R z?}A%Ag+KqTeKu`HKxTo<1@Lcx0q8O4BHx5W!&k>1#U$!{(-PRW%2Sl@u#i2At&`sI>M)KgH$s-2bOxWKwL0*|1P{;^!DR+V@^g|jWLsZKW@Y? z34OyD$RdtEMRA>yX?K7E{^uGb0a+u?)P*Gz3X_ zqUPS|?r%V2K$iF%q}v0>mT`59(!7T*@&5rYdRyw08Y%Sl`fVJwMVUKRd2Y_tc@LQX z0NG<_PBIrI+I@4b|7Ziye>fkOMp_3O#KfohS03rp)PF@DXETS?X$r*yvSpNFe(wn8 zqp{7+1rAO+|Ch(J2xZJ7Z;pJ`g?ibk!fR(qK6h<2yF)TLXuJ4m73efMzQ~9LE8YCc zmzn8rcI*r20U1re>3@+rm*8H|OpEi}IqQjZJjlR@r__u8k@ceA20OKbmA+CPt(SO{ zw_+1rRIHwWIqX;#&+u%q=&gFY2Y-9hvFycCZVu{ANR=>rlz>nRj;(jP25;zjvQK&2 zBYCS$r(Gc%H)5@zB34Ezn9&WffC(nggpm%7jG#S`R}L*Z&VNCjgi`3B_ev zeDq-x4$XV5m5WqbkHrG&eSgxln4N&%$2220@P+Fwn<4~1S}lfsylX6k*uTyk4XQ0D zyL*`N1<#cz5_~Owc8`jq;v)Tv2a*@nzP|_3p`N1Pp=Y@a8cDpUc>eY&%Z~ck%6^N5 zO7lv``W$YlWBo6;jt81VnjFljtt$PZJ>;fJ9mbIcv9K5RPH#5)(}7y?Y7)Q|`v2FK z&S7mSNSKkQwzBwgIqx$Hyw#~ot5Zxp84fVlXmVPU{PYRM|1qMGg+Q%@BC?fPDn5R? zogfBKV(S2G0&ar4m1t;4k5SUJr4G?oV8?Vt_F&kM`5{GO0&4*1$YM2JE;-oq%o9rT z)h(aLU(Z#e)LM`7K+J&sK!y^!qz*WV1SGr2i?5lPVrhuD(d@GWtCOTug}n4Cj%#e> zz35wk?4lkAgv(l_F(`AensvPcqs{(%DIi&mhHi7YgjS%1w2n5W}wYD8Jf zL4tw=NBKf>TN+8!U_z_ksb-ZtLIG&(mAqZy*WJ?$(yDa-frX-t7;{z8PY8WJwQ?1{g{L%(y$|f_z$xd;SOa`)3o!N$w&Uzrh-c@vt_p3IJ{Nw( z+0c96L%Xvci&09cW23~Mo#IxANbJz%=4Nw6y1(~g_p7-E8y-n2y&^(`DJs2n{*=t{ zl0jT36jTR&ak6Auw>xj&0)%y&%NfwX)}$|n5H;R<^9o;aM0^wFSrP5 zU2$qb{4j$I&$c=6;_Z?VTo8wsM%i)?j38L1d4~hJwhpQ)3u=0{^^2b4Zxj-19UQvWwgHgfqgYf6@?@_h^UeC0UL=5(z-YFG$Mh6jG~C-^A(vKWnH+Mp zMZo?0Z&m>c13@6Ef0gt~ORyX(wh48yB^1p24R)EkN~U~5C1*f+_4modNVH zHa~w?KZL;qEd#llM1K8rV^L1j2c=uIj}hTCYF0lcEBS=U4dsIqRyjXDPz;L0L`HMewF?;@(H1ALHXbhCtWppO7ZU_p8t$CwlF;M>ydGp-+wKDFrgV z#jp;#dU3OM)0tdFivW>x6D}irk16Zi1R_O2EuSS1VoNX)^I(VUo+MrW@MKQG+~xj= zeTC`8;f4o5;i~@EO4Fir#$EOXjGJ_%d}uAGNBT8tY?bGtD4%W^1K_vcY;exrRf0(a zc=Ay_9eEHvN4>?8D;wYs)lr*OE)^gnUp0pCDM!M1np-S+oT?8i4M0>(PvS5p%X(vY z{iR0F2j=<*JbN@8y{;!@+eJj{4nr>@KTruRUI`U59t>H3R}PrO@k{?RsaE83D2M#A z*V}d`L8Ftjnh1c?YpPb~ox?~``~TwGJCG-@m)6Xn#y$D_YHMm)?6_)VxJV5CxFZlLSqHM0{Zc?c?{r(=nS>C)L&uXyl2FRE?wHoW)z zvR&wo;c6dmqjy712aIuLZ_Ujy0}7HS%mm87S`&gGPxzVBk=CK5>mRK<((k&?FMk8s z`71ak_u#O{hgR@wa@Z{=tv1e_A;AkRWW9w`_+ z83?7!KaJ6qVDW0fBK;=uK*8j=G6+!Im9c(u5(wLMKR2+_aq-#E)^hRbXURpaB(1nI zC*{%5<|E_E|B(2*zj9dO!VBl`Su0exi=fuO9fOmnO$qB@%+p@+O|=c!0J8k)rrpUK z4rg{*7x2U3t67J_7Z*6blM=S;6fa4^r;J$3FPCYMYnmsOZmA$t6h8-66xlL?^F8`N zmmdaTfpQ!QTV)emxH~-;5WXa=&q(}ubV?f;IXE{OxNf!uo|1*^T(98Udqq!w7(!!& zmkmliv!wcD?o^Xclo8`n0CKL*v=ARhe-p z@bWH4b+)kwnVO0}`6z zg`FJ&W?N1!3QRA~KNdvwPBM`1O%LBpRIeV&~?7+MR z75?JzuNg@6-mze+D8VOGAFxzZv*#Z0zIZpvLpSH_l2TxTd$OSH0VZ)6^0ts*p@0YY0m6`Em zck=N7rkgU|x*5ErzBeORMYg9n8k<+|AFVkns?yQ%4w+=CvW4nmaINkM*kYJPh;#hq+aPRqa`~}zlS$3*Q=fUmJd_s zBrZ<7((1b3lUsXcBprJMCE7#`V@x=g2V-V)P%|hP+>*uJNjK$v5n$DC1%f#EOzE!!OPJw!R@_HD5^S)luv5&xIijQrfNl>59ii(ID=^ z14`U>qWBAX5roER#97@uGnaR`?^k?tJHZQI8q97 z;HsqT5l^$MrOW>jKt#7ivB5A?*;b!E#r==DXS-ecVtaD+p(s@-bbRDrkEW~chpek1 zO~Ur?3?(zB6|E<#j%}OApG%XDn9F$8t`Sitjz43LY(ULQ`s~UmhOA9wnd-EsSCn1N z3adIbd#}6Q(3Zl=irm&3BCLNoHwRQaxiMcDmRaE^d1%dTXb+g{x6XR-LN)XE*<)7j z&ZFM48l_@^Y>Ln>@u$+nEV9gnhc1lwV&*{JU)L=@aV1FV{XS4SZT$9hX{tEBC|OZ_ z<}>UNvjR4}F5?S~q1;OBU!o~uCD`K?x;frWM=!)lFiU`h;?L4Vea9^zR;eqob$4m( zYkBKgmmOH@ulYy~@CEqfZ_j5i!CzpxghAyx5PA6t8}sb*FJ@U>P|TIyK$7?gg=JD= zF1NgO$J_k3%Ad?%38yz`8?UOpFMRO{bIH768*)}0}pV_E|43|z6D=ZB`%Kff0k=KD%X@nz8T;i z?XeJVJLKgVP?|3!_NT6^u6MR{xZQc!)cqc6&G+>aAXh#4vT#Bo-!%I8@fL&L#a#vK z)O^$jDtIXsFv*Z+=#ce?fzUZp4P6omOz-EvuFcw7n^ZllIPTim6(i3c#^-5rM1Htx z*D(sxGFK>SkJd?k(U}(`U46IH(7fYf<80*B+~uG2xxeFbMjh3-=G^yd_x>&PnS$WI z{Z%W-_Etm<64PqbMBhRhfmsY)BPyw+C9z-2m2u<2YH}iXohv2kk~-zhuZrO1_PHJ2XwGv5>RxpRbYuD5Rg25 zsEOtwib>GpH6ll?oxDrn%shYkw3&Vex$FD2uVB`{h~|yUPhuzP3M})wM1Fc-?=?v; zM*#HRgKJ^ZB!{UGd%iF=@HQ&Z@d1p;MOdS53(p$Rr7ZDMNIzttuwY(F=(Z z%vJ<3EW!2Fte3{7@gv;TWwVA*j>1h~gMpj~IL<-gHCMImCsGix4kwZLC+$SOh5nI5 zJnQCtus?zyTPOS|n3vYpU&Z?hwOq zM|?UlP1ICZ z6QIx;y5CIuQllWm>G1c%cW7qfKJ^A@{;n3cH-4@A)WSZTn1*Z+z2eDf zRQD00`;_wDkl0gl4pF^=7n-uM7tht5HZx0;UA_(exVR`9kp`A@|DKLRV&yJX-C$RB zU08>o`q)3E?`PClx=W?xgPZUehV^aRl67K#K4UiBB7dPj7mdB7)%kT{er>4|&Ev~f zxcLUYSv-G4sGS#%+klW?hdmc)_d&AlWWWLpFpgC>Ma`Lzmt`x??h^{l+!l$!jvBQC z}65R`CLZ`aL-0petJh9;nbw$}m32i7Rc<5lnaA4-*;6iJ$N31b)`4 zKj9mbvDLLCT35=Vt2TSVKk|>USnxtmak~Zj;Kv)gQ2NJdnaxZbz55mXc=!y+QTV3# zE5@lDRKUL4b(g|=A%hgZZ+0WPCZeu96R3Q(9fr=d9 zNO`C+8DbPY=e<`n^S|)s*zmVw)}8&yu!2)w3FUsJX4ZHnAN=?aFZ1poLWD(g(~RK$ zN>FzXbRI>((Hl`g#fMEw*0Rt$h?!pntc6rzC$PBYY>c|J(r?=!nn!hbdnv8K==}d- zrHsC5oVY7hpqd$DK}?9?rfFw*0yL=iMDrpTnrs@U;&tDQs@JHFXNv zdse~Z^h`)W0ltI8P(j<<#ew1wSm-!3a)bkq|TAspDv_NKb)^-b=FItnoa%$b`jejgQjnI1Txpx@$Mt z)9%VKJ$>@T@oUvTwCE*(>3-u2f#yNjjp-J$m$&ga1aZ*zp43enP^dMxz;|fX5|02> zcZo+H9vRaHQ7+rlb3UE#28`L;o@5Ds1{axs6ov)NKnhTg43o*_kv8Z*EZ6nCPno|_ zuzO4u`DJb!8oiSK(!0H(XGvts6i6~Dkm8dCbLrtzdy`*Eo&A;VKxGjez76)b6Jot6 zyBqPFv`QVKS;5jQ&e0eq24!fa2*o2z)wjC3T~C=rluw^wBJX9*acLL0KC*w zt2uHQvBUA2Jb+8d7#ic6U=c!DT}-1@*x8$xvry2v+2W^hc1ONaq;*_V)Y|o;n<9IS zJ5zdrz@ekh|Ir5{Wp3dJsg4e zWxHuV3dgNR4l6x#P<^DxiN~B`!w5!i z{3h12B*F=Zb39To4m~Oj;Dx1DhvKpqbvD^hP|2?3I1G35p=<;T;w?PS*kf@7I!3KL zZ54?gLv_p?^)JH!RpcoUW=ncy2#^32>hl5jyjpi}wF*iky)(apb=z38 z(LS%=GxzH0aM?Y0DMT=Fbv;Yo?<2w5{ZL_*;5_p~rjfG!=RG^&6qDtI8|`xX9U_8m zw}Qcxq^S}iQnSm(n9DL44H$4KA#Z?yAv6jWix80KS0IXxNozgEPrt^})xRa<1mdB6 zPapkV%5V68 z#aB1N`rM-{;hqIdt#bA39Nt;8EQ3Px=`Z|21jC;%OBqe2AE0gT?%sZ1_J=a;s_l$v zH*EtXd4tc}5g^)y{-E0Q$yl8eFI0k*k8;_Rx-8tnrDj-RX;AM2-+|9+(tS@k+L@CX z*?(-FjLEEokiUa9q`iowPhjjQ`YwURHz!JW}`RS8CzyHo9U7_xo(nGa;c~8p;uxSIJ`uAk$c%udi82B43kRg9Sx0r&N#&M)ZAnfNLB+z+nZS7C;f+& zz=GlcT1u}KZ5ll4j4)Xu|NnUU%CM-~u5Afv7`ho41f;uRsG++X>5%SJN??YNloX^J zDM3<^?i2|@xIsw~Bm@cJy?DOk`2O&BHhW*O*1693J}^#1MVovle3X2%Y)5$_UmvNM zV*T<(XoJS}r=;ymRyL)BVth*fo%m4dlJCCRE(2`Y7xACaQ5t(Nl3%mHy_QirxKRay zF-{w2qC}pGkiTt?3knT(Q!T(31N>+`*`-IOA1jS*%^8Uur-DeYZ0Snw&P?uVKwU&2 z0m>qz#JJGe%%Bcf>%TZ(fzoK6t9GVyE2m$~!yt*rGEL$Q3h%A5N-Xn;!1eR3bffs1 z>b^fwt69^}80y35nJ9@}gZ>H%j}bp54r|ZuTYdlQ-}Qypw@%i=4g%-q|3c!PsRufT zE1S%$-K!dd!v+W$*}S7OF~7BJykpLF>Mj7^tClrp@KLxf)*&Vqfqv%~8Yz;W#+MZa zztUdbTj9O1YRAJ@6GkHh>9{mL8`Z2DTRAD=0k$Y|wk}Py_Ei0%emy;>;f6#vCMT2Q zeU}5+mK?<1E4%G3`ZOC>7+o-C*K_t6VR~SIkHn3;_CxD>;oEN&mAw!4cASYht_%*^7+kw(a>7wGc zTR|x?mWot_K4p0OMeD|Me#FBT$=*i^APl9la@v3EPV6`OlX`u)3Czfx+UO`2%qXE%16+Dc-|8B1org3rE z7D>DmTw-&tU~zEjdb>KH5(u{h6b)U}{vb%_^@^8iQET`N8mxJ&BE);9B8UWFV)4xF zkz|%k88BmeTJo{@dSa!VbE3l`zSEiavz$lAMSfQ-9IkmA6c8)3F_9`r!9h8s8KwX2 z6JcZNuptkS-7Qe~;r?eH_ZCz7t)I#R;mRi$_xO+v3ebLZ_+rxlSxh0#?CeR}#sfip z2O<(Zm@@+f-9lV8#hfrr8NY-RRg{yLiz@S1jvuv)2e(`j!WIccmFAM+<>e0q@cCTg zpn+aCr%!|#X8J$%?f{7Qd8)UMJaJ5rE3k08+Jg}+%DA&Wc0l{&wnd;9Mr?-{Dy}IZ zJk?rjXG7(U7}Xx=H2S#566T;JN?S5A0nHkrPIaNG=K_b|+yn+~x)3{y*trVXMvSlq zM!eLVcer5oIT>kqyER4{+*U$=ZFeI5*2*F0ttJu3-Q#V+hfmZ$uv>s`yWZ~s^wSgI z{Qy!`?f7iC1T3u^tEMGv|F`ZF)hjb=M(7m+i$*kc9kL3r)g+wE*@x+?A25fE@(!ok zKk0P%xC?vuO3~kJ{6jNLsWy1GEz7L=*{xgVd24(S*(o^xPBroOY?m*`yU9nxT3}sf z-nW7`(@8{bCtLs_0Ym&wv=&ADt2wZYu;=}-i} zgO{cCU%SSe2P2kGzC4Xj#E*MN-qk4iy8M6D)#|S8mL{Y&M`ApEcZRMVvyWDN5razF z{Hv^K@u#y2Nyg1l0lIZ8l|`!-V;p4%0wssbG1ketTre!i@rqaqmM2u4Y|!OBYrJbs zfwPpr0iy@W82TA8iVi~9tX|wT0_>f8px5ZvE8kF57bP3n`>&rxE<#bEfyLeGD2+nD zdnYg;UtF-C@s5M?GUKPwYSb&}IvCG%Y|knrFWbLWQX)n8gJoZgNKdy9v=p>HOO5`M z9d8ksyUE2D=E8f8TjRgYS3>$|&0DXdpN1-Qy5O)b44c-8_re;CIe$DP#y=I^K~hPj zP+X2Hy$N=E?K}Xj-&q9)bX#F5(YAvBf*vP9-?-oNoxarH!y}e?@8~JE@8nL9p6 zE;peNKr1IdE&b?b=DTQBE_A;`8)O#n=XbWBovd64gf6j^BcU7#Mr@QeZ;|#l?zP3%C5v zR!~h~U@4a516}*GfVW?Z8{HZC;7MtZ6VXu{UBE|yaHn5#%b*z;%Ot^XZgym?jWTa@ zEq_gtoe>)kkuhe&-1q!=W1yl$XN~5n5NAurppn(~?#@FqN^g`+JtX+yOflP|S96veh{Hjo37@r3~izxpGEy`sBDl#vPa#Q`74|L$a30jcKz_G0dEgB^^5?D)qk0PP0vGU!YY3lvXfM>eF>zeP5wJBJh~RGEqB?$@*JjUA;k z;)lfV);O{lYz?=b7~T3VV8idt2RCT72|yNOH3$V;mgyXGd_%KJWg;ZMK;&3w9cou2>`Ob}#LdfDGfOPQinHkrgZ74AS zFm}_UcjVQio8#>t(FuH|{>Xb=UW&)hG0Rm|*42CC);OZ*v zRVCQ^mb4}9sK;y$DDuWmurh^_XiNmV)!oAt=u<0uVXrNr&+4bCu)PWLT1RcKDns#%)@kWCWk zMSMfFC)cnMUyg?`h%~);Q`5HbHibA0M;e^y%!7^Nl4MzP{cJ?=GJB$$#L~mL7mVns zUX0C@6vKqYBXwt3%*?~|*Q*-5!}Jy^O2BgJnWGUwn*$z?ElQ~8>N$?3%+3CHxv|RR z&|xSbKg!+?-vx&>#N|@TX<9^*UXJH1b#s?4A9r~=ri_4JbzmZUeh*}IIvx#+e{t^>=$d+b*{`U)aQpb}q%9Q#;s$iEa!1GAZ z$dgEg31YE;X|TKOjrrb%k;1O+k2*5! zOkDGa>mE9uy)!SF3YZ40c&SSImEAa=IHJ~iLQDIr!F*6;;|QTgVF+T0^;GguaX@Yp zlc!|wh{u$5<@r{+o-BMkue&DS4gwMn+Usrk8!{N9In6Y!UQH^2Idc|8CrInognUCo z8+@v1?r6jw;YeZrlKEX9iMfbWy;<^dT;HORgaF5wR*fyh{;`$4{c;uhsv8!WSz)JSKjgwQ zROkXe0CfuWaDqEgYf9zz<@|ZwddEQmuY1o3W&o;kK#XbQS+x`YX0R*vI}_k8D*VV< zRx8nXx4M<{A-hpX5%0e^@(fts@(Vilv1ctbgT#6pCqJ@>pogQp?>-~`=7+|;yxBUz z!tfW|Fmj{}Lm}crUi2;m!d$5RhQx^7?mk;za#1kC-P7m#!AfFRN`6P`n5kMaF((!k zfFnG9#FiMH(2c^&tS?m{S{_>yA^zZ(#UY+;CYP8?$t!!)xm5Tn{=1YqifoAz+q4(KBQ4{`$nFvdLzp}1?O(91)y z$q3zqB{N~-75W$X0*Nmzs8IpG_$pl=NzTL~*k!Z=sRJsR$G#LXypJp}!n2-l-vw1e zIJML{IKe2c9t=9?)iTsv7>{M>Y;+PjsX#U^?bmD?DV~tvD4c~&NzC;RML|V1c8h(y zgBBx+UNT8K;7T~i%Z|#YU&MXFGtw%GjK&iHKZu!>qplL$R|JdS<;%!E5RB&^kdx26 z6`(!4?S0}tl?Ej$*pUlmC(g(jiM%|2VbZ>Dr>p!OHtlDzA?d z_%Z+zUu}zu8ozGPI-vrNj1|DGGyS$d6?%R@&Kka5V(iDaOzV#`h@!D6Bs4ORWTjBr z--FqCdrS1&bepIsU$M|Czn)A28!E#p2~_s4ziDp+J}2;z^au9!;s$t8uYT{8gzGuw z-U`tce`!g{sut_=rU1D%Pm-6fO#~Py?xljB%;N)P1qvF%M#;Qod)6QR=#Q8rcG!MI z$9PtJTV5G~mRExOwsX%8MCd?X3RIJF_IfTfT7N}1$w)!VrW-%h9E1i24VIq~w+XH) z*z)K6pdeIo%HGN6hp%!4v1>w1P(*$^aZMNmMyqw?YqHfmb-Yiny z+P+Nl+u zSuPDNXZ&stDTegaKAv#$Mds|)_2rJP*5;KVkz#TVye|a7*6Os){V*HXWmo|m^N=O< zrW1#+^PSR=?m({ROChwfttR9v2$+xkS28%kQPO-?P+j}0bw?pcBktDk|F;RlcYhhz zS3=YC$9|h5xe*Fz1NzoK!BoD)L_`JTQcpid!MXpl?0Z_cxFLro16RQtW?tL_x$kbq zp)RYtFo~|8hd7ZRSO@uns(PfE_o`Bd^dK>+Is7ols&}N%yNv3)HQ{|u!OK8l7aP}tNkm$EAQ(GZiBr^H&EcB9%0c-B^h_T* z0V7Aok0xAOdBtdo_J^AA)F;8XeF13pgd^=MH@O4Ylts>anza@B;F3=<_UV4jb9=?4 z1&&3C`hf2NtTO;0)i^`}Ac(8A)5q-ynz|cB(&3N?1a}u*W8fLA#{mS`? z+QHS}p1`om#3`_{tgjj|m!NQR-5>1SUgVzq$)$0}4L9VRanMcU$d0}N?U$HUZd;X( zU&-WrAADaSEk+_nUjh0`{w(526jE~5_7H|V&>_E6nIBshRSsHLUQxu+Y+3!4g05nl zG(w%Qn>{7{`H2wXOx5*`#AbM^Peraj<|Y(Q|IyDMQ0~CXWb%)_<^?^Su1(gallNUS!N`5|0#9N3WjL4PKRywUzGc0}HgE2}GgtdqfV__@x3Yt1=p883 zHq-`@Ei)2K7K`T`Q2(pVaI9 zHEoif%TrtZCv1lv03`tx^xdxra3lY;2SmfJF%W=% z_&hiaKzSEJM7pZ?ce)miIQb?*6N-9`F=eW0r5uKhXM7{k4H);Bq110#(HKr1;-*)G zvxM=cIck^vwLVZ=I4YaPo9L>UlfI`cldA&z@7-d@QR4>drleO9I^0dtUXY%mJY z&|YQcPN$MIT#_MH*xQKPiWlUfd2k*1cD}-~B`2G+N>}a=uvw5`c?dZBMrWy;fHTMz zw@XO2V*mSAw~$G4cv%zJNG#kJ920Q(O=-kgO1&0up#Tx^eQ!T>^3{j9sXU`1VrT(f^6eim2YNu8FpCJBqr$~OXBcABvMazjtM*9b zIO;SAs?MPs(@V@P6U^qQPR0{>NJmO0R>n$@RuS=MHzubg=esh~)C@v$#0<)IKS_dH zB08e+Ru!@~$~hxZWE?-MO%);F)Q5z&_qFp7kYcxVhdfY@Ieud}JI($OpyAs%N?6dW z7$S|WxEV+72qF3pVM@D}%86!7Otai~9Q^yK=n5nq>dd@&8fUlC>o5%v>)e{mTe*zQ ztMlJV32;;i6`!yStVGtdr-X~)%FtQ*VPaBsi|082+6F0kEy$ zaBbf!j-N??6)%V!;To@NZ43QxaH!T3q&N{APLr_dDkm;~IG%NaMvtpR__{N)&q()B zmQo5O-U>2gSXG#PbJ)zXsfab7__xS8S33oz{nINyfQL)gh3#|M=QF16gfJb(P zO^r6X_4i=iKC5``6*%u=E_++E+JI}eITzgjVwAx`KPJnp6p=yB&`ewy^LwN;V)Yu+ zX{k({Q8T7T2ghFV9Shc~PcfrW(y<69xNIa&YuB-dxB46O^m9O*2+#+A<%y)ugeu6h z4kcr7_Il^UwaEkM0iwYO2G4eo6^`AQd<7Kq-qZ)1 zPZ07I=hTEPeOP&k1v{Tu5Ao5mb+SQ{;b_M7 zSK9roEd#&9oy5U!lsor!YAIf>Ft6>kcaQsz$-P-SCtTIDAylGp-uI^h84byJ5*k8} zol(hOCk@8`wd6=VBK~G%;F1Sj>xsBK|Fz<3|G%R&UIkCic*t|RMCMfjw4Nn`rcHNM zjQ?ef?m&EQZwt<{FJKR(18**uRdqKC%3$hDEXouj6W+0W2fxrl^3t{z@qe{*c9cUX zZkoxoxm+Pfl<>b=e9a$U>^%F{PaxLLK%`l9?kmY_)AP*1K#lG|QFt-CV@9%oJ9vv5E37kaEZB<)s{56KdHia?Tx zxGRz@TB7$7%fNSp`hEQysBd)|ucH7~$%JMti*Y0j^eL@`<_0BB+`{>2;f$f5+t8rF`@%LfO~osL}-KgHXV)~P81TZc5@*400Y2v~L^B&XK2KD3ix(_t_pEXKuw@-@I{%*f;fgob($?1v}pX zgWDfpA}g^ri-8E|Ty#seAqwmlrO_u$eMD^D{vZdq4YLU;KFm+Yf3%Dx(%+llhQGH_ z9Ic)PBnSD9Mg7#Z?=BuKzkA<{8R$u-6-{`^QcU@dQTYca&=ydTfNpQ9jo4q@axEsN zQ@o~P2J@a|6dpYu%9698+xtTYWAd?S?_=3rm^O?$`BmIgY{Y=!Kpy?K`>1Szc1_Cr zW;y3vBJarHS@;iQhps4oy(fthP-V-q*#oMCU~&?Dc}Y(0SRyN0Mx}&X5#nzm*3z(dH-!i8IkNPZeH#G z&A8Yp#di+hx}SF*xousTf7gP9QPBoKj-TLLlV|;jA5egJt$%#EgrMdXrcOp@lP@hV z1)7XIwY@Wac7^d^y@VzV3Rc`A4pmFXhW@3CI-@8Bcm~-VtScICuOG~J{#9*GRHgr{ z5*GwcAR!Nph<8{ypPL4#0vxbrkL>xR?v_c^5Ho+=PoNY>g%RKzB?w-r>zt< z-B9k-etdgz_&v?7*B33cDj&BV=DhrxF8v^(Ow%$}afIQ`u)y}rNdU+j`C+76Z1$2I zPu2AasB_UC5?3=ZP~dSrA4&gqP+zLg6M%c)A1(DWUKKM&kEacgS;R+=)eO}cw`G^P zCaE8e0Ic=!OJlAdMkO|{>4e-b-j1r%>JnR8A ztLJDcH2iE!&=kpzwH5f7f2`)S6H&%iydfTJnd^+sEAtKx)wq2X_Tsqvn780u1kL2()(`-r#8k-AndM2B{A2yqO|$! z%z`{?>0j3aCMdu=fbE+=WII(@FbJX!3C&5v6zU!b7E1Y!Zm?$r^P|D)x00;HXfaR1 ziP@JBGT$pMJ$R&;YyZ1|d&ai6WTGsNn)BJd6wxRi5keW2yZV>O$3*~ShPw!WW6x>S zCGa6Ctbz7J|LTWW`n~~3Le{lQk$88FmYW-dH5kaB$FH9llrAiyMmrL0Mj&|b9(a~vdo zdS#Mphtk&h!4axDyblG?m^du0ySWd5SK+qb?QZBk_8kY_n6|$BY&_ke{o&8VR=!X3>|x9)mH0^7tnAp^*j1-0kwmSe(4Nxv`pvo^#mrO~{$*L6~1E2ofU+kR#qC=!v$An&y-tud1pyBO-)YrH!?8B@< zM&k#qMvV9~2$Y}-n|iGR(hPx+{%*BBpuB4)hsy2q&ElX{CLmBc({P?rN|fMd@|mUC zqRKLz?C#|*L0%ad{P%D#SB$;N!C!bWQ2cxsro}Y{{6D>iBRBrDi|~6k1tOP~K`vAg z(pKpE?2;FST8N`TLOY(?UZI?u(scJjJb^@xD%~8^Sc%c9V|MW%{dNDuiR<+J>jDGF z9nqtQP@+B9li|hRQwfVc=QNSb>aGo(DNFsEDN^@SKq^}9IX{4YijM6Tm)1^fYLhAE zW~U5zk+I;@_XC1uF9a+03}RUoVrQSB_+1lsxiu%-RTz=D>;vpH^;mNX7v-eqEuqHJ ztJiIQufqVwUt%Vl?H3Zjd(bK+fy1@(DEdeAZ~x0b{D6C32o!ASM3VpYjqPAQ{-mBP z=$mWePd-+T1cFF+&DedxpK3(=?O^M)%oX?b!8arXb#@z90(^w2%a} zd8{ysME3r#in-qdezvI}@9Ij3&7Zc2=DIH?K-4mcg_MR+8fLJ;MY|`4$Eo^4ib0J4 zFfR82w@o;Z_*_`i?7^fdv-bLgH--C?`Oa#FgtEKt^Srd@Tdm~9k+zK|Z~T^L5o;}u zq+ZHgIMVat4}d-opRn|bjJC`wH?BU@Cd{xO_Q-1Lfr}K=OoX>ov=qMo&Rgbo*4+h4w1Czv9{okR+eK#+lt{%#Gin(yzv@}wC z+*#v_$bdh)f*D1(xbwMGTH3yQ9uSxl8qapQERXqA)nIp3lZ+C(fbq_Vvmpe= zbJfXA0}5=`WN_)cDy2(}3Z~yPUGOcoG0pveTs1f=Io+EtO)o^-8Sk}OWCyieCiVcD0zj?z}jC5OKrng#hg|uj*De{Kqu$&Xi zxAW8cn;rJQk3jq1)F~A7afCmCV?M+X6nd79 zSzf+|D)P9A_KOE%Qv2)TTvg?Bm!l*XadqsQKRlxU8Pg4>LEocd2FUT+J6KFgwi zqTfb*gF{lo=CT2J0%3--1a%3!rVo5bgjsgR;s%C^`|%%5 z-HgQ&bv1l}!SkB|d8=6V3~BmAnd0M{&6g4vxV=76h8(2F`8FRc)wV>@;nu7q9=K}ie0Hm{q9jn{7O z+4hOea#y$+4U-38ElM=n?ZQ5>D#tR!Y^+AUl-Um7^7`kmWtdT|b;tN6j@Evv=A0|L zcD={9IIkS}SpO`QHRG_#t)+@Ev%F5>YK-9ujXbv%h4GICsiGl;Es*s0?8j>dvW`zx z4?u};e80B>5a>W4u`~m$C|Z1yXi>a0lUfZO(*AFr>f#=krpN$+g33C_#@6N^22Tp2 z7HC@n=wfDnI6Cn5dvm?$wQ*(iyGtp&-WzgX``tC(xfA;BvHgVA=i94R{JSq1=bsr1 zfw!w<4wN?_BkL+(V?`IQPgM=}h~u+a9Pc}C*>t$GB>(GB9S`rvCkhiPD-$=8cNy2d z!?A!Cm^3#L_S%A_Srh+Z9?<@#c$O45Eg_iq_cng5v^}b>sQc`e(D^j@h4`dRm?y>k zy~Ce#WKh==VRz)8JZ63^d;2RHs(+XCb+h~M&b_TjFs|y~zT@eOM5?=i{uI7e%Cl7B zU~0mPks^>VJnAV#EBD`0-TVV|+fC;5Dl$OC_~^2sr>Oc7@S0(R!G$|iq5X=(Mpq4! z#8ii~{Z)D6X8%#MB&ZY7`C~n%KFWdbZouTuijd+Nw(S#OG8bv)cDK`W`uU~6kZCgm z@9Jjv4*x6xy*5HICGULQO6qb|=rkhYZ;N4QmE|i5tXMS3qz}yFfC&LGO}|pyi}9h= zR6^5lCnAAQDWgfUHwRO76(Kb_R0uFPoJ<#TeQIh)hVV0+Xp>j#3jQ};7p?Co-gO3< zO99^bPi%qu3O;D{5X&G;hg}>;!0p|_Enmb%!gh7>@d?~eIf6d{ zSyxcK_ViEve~J)m!*3`0=^Aj>$Q*%V?W6buwD@r9G_lg$rU%Rn-y?q*Z>1_yZ$8^O zc4eA6Tks$7-qkp&u88TPi4^o!7ZA-UvruB4L*#$rr{?}Li7k(+VOur7?z#Jr4E016 zFUL>>)-rNF3OYKqs*DS~UF=@Z2Wz|iMuqXs-C~NYwgMy;18Xt&J%J;|Xh26OYSySv zt8NSHV^G)j&q-eEL@uV;hmTI2gz+nd_*|JLh`-UrfEDxRLdlRwB-`&IW+TAf_mvK9 zGWvJ=xS9Ub(^F4eJBY2Lb&hzGZR4{^^J}lW+v$8m3;%Z3I4kCNFy`j%5be_$R{jDi z=a`|n$zW=-WvrV#e-Uvef4&~rFm(K za?gz^8j6>itW^75?o)bUlUayS@mZtC%1WCTkc4p$ zy7$}Eb~5|gmd5SM-t-qf{?Dd6w4MUZCoDwnmiW4RQ$@evum!xD7y)b(cU!Yw@b_fvURg`_m-?Yz!E z!;y~;&)L2U#`M|%L{xrgwu5^vwhOrksQ|s z=%#YfvN^K>$?}5d4?}Zi!R=L2niWzrH%?~3d2`9OA=2kZKMt~=be&YnxZZg|3L`=2 zSId?Ndo8hlu1G+YgGJ5J9P*87%v5Q*A{xxed4|aRAc6cM5m4Qod(Be#EhkubE<2 zLeEjtZTjvX{@Eax$=%%EcdF?X<~>`rr{lStwKJ~%xg26oozQs+UUQ9u6iahGeN=9@ zDU4^r{?J5{pIVfsY-~*ifYuPW+ZcA_6;GhowQTg+Jj}Ce>wW9Ixof<+#saVb z$#U25|7hmvX7O)PXCh@iO!zzJ3+Q(G`~0ve-{f8XN1mv*7*x<7|9_?nS)zRniCIW) z>CTv+wcmA03;)qh@SXkTZ|%lSf*WQ^=3&}?$`~li6mFw=*^M3hEhnub$$%)XI<7aN z(iGSdNAXVW&mPq_e=%7mR{HG5dvBxIqAis#`Y@Mh&;Jj2UL8m>)pYBrq{0jz5|x#wK2D#0F4j1+V%489Q}&EGW@ zk$kBlUB|NMz9v&ISd{&}IrTHk^q>?XAu)<`-*H{mcE~+o z+fS`)-C1Z+1X09B&hIT0T19poEHvUKz!Ev=`1apO<@UjPx8eOAbXu9hdywTqfFvJG z6eJ_ff4)NIpZjR_3HjP_`fIJWanU+tIm@i?*#z%^CXp-j6#t*1;RqUjx<#Cz1eMsC zhK|fvrbaC0j)0|s^U}EmUh<`dk~#-!f}!7$j5GaHuOEVIF%IX7`uKJwFwyiCIcGTSR>i8#wvt4I^o`fB|Xzwg!u>|LNRwa5hfa} z_Tn9~U{KL<%LsN(>`X;~r4iD=&WEG7sBL!L)${jVz%<_IeB6FQ;4Un;vm{Q_$fFP9 zhWKJ^9gA``Fl&x;A(K(f8ZmV=#9`uV-T_I$cTZPokttwW}+qsCcvBF+vg zFZ*p!b$bIwBQ5i{$lCM7Z0o-oLC_xmKq!hp_e@t!V+`<39Baoleg2xKQ+4UDvsIph znn~A?!pWREl^XX^9KJvXl>iHN36+s}Eam$kkO%X&6g`rC$42GPbf(nZ)dj;D3%*nt z2$ap9L!7@zeuskvD%z;&j`i!oLXY=*qy54C;kE`IAC2Skv=to_Nw`1>StIE@dd8ap z`)tnqt~z)L_gv6eZI=2Y`{r%%2o-Ts@) zjiuR~ncf|bey6uXkaVx^t1L(IQ+tYlnr5W#-v{fP%S&)~+nvbEJCK;A$xU!qGMr^gTsC!mE6_q>BH&_d0BPE&SwI zzd7>2iI7Ll;1#ocwwb)w(>DD+jm3?(1ppClEs;`7LX!Lw%v(l~q%bSkk!ssIqoSfY z6Se7pt&B!Ns$KUhkuHm`=Wu11wrL=lNM?t0U6d&&ock}Pal2c))h8R?Y}_(KJLw%F zdx%aA@9!DmAyKOXm=mZHb z53LGT$=U;nTp9AFW<%giJ1L}**_sJH4Z(6U-5avp;&!}(f76S#V)Y+?UrIsv-e;YF zNLFcw9hDhJ(7-0ur=RtdAh*{h-&Bi1(%IulIt26j5hV5_H4X#BdKe_PLV=3cGMu>y z=`1-RKcUgYy*B*Rn%RHO8XE#s=M5{C+4#gV_hj-78Fb2cOrL4falIS0V=H-*xa?Ul zxj^ey)tW_6VDXt+@|~ecMeF%Q`W`w2Sm@f_%-qZ+9~UsWNmHS*l zt1X@k$8NS(6^qCAsABMsx^@qzM>#R55EXD6f8$G}Ty0ZH?nx zF|X13V(vQf#%rJM>(`lF5sKx!5yS0OeIt*M`v_`(5>(b#0iVo3rh4D?{C}s}I=2ms z)>rXTz;J*!vIMCorX_(5ttVzs4)xHw6|K7rxY-5$;)2Jg{j9F%q~PhP^ok$2sLp~q z_HlB%_g?&fF@~NH`b0073IZEtUALD_;Gog2Xf-61+sG~d znkVGOrI9IO)Wz}K)Z>SNrqcNV2x&Iq%YVMpS{reG`|fW1d&)OBXz79(^rxbbG{uP4 zRsK;8?=-Kq()$q=dkrYY2~^3+>?+8aIm=RDz+t0mkoem3#RFz~U+P4%XthtpjHflp zi$JXpitzjTw(-V22};BXm8nF2>p?Uwfef%A!=Z&CL&cXc`zpaW)c(uRu|R_%Q#Uuk zx5ZhSZq=+7B&|TmXQ45aHwG5aP~O+M5Vx+GKA~nK$Eo*uoOj?+ua~n9I*%}W+zPMj zdw$C`>B?P3w>=yYd#R`&x%BOoqF%j7!eM;QrJs}XXJ0L?k=A+F(p+jS6?Sr-V1wUK z7CB-}wNyEZRiQuoxz~phxnbd|ekWy8xgA=E6F1?dzc*K<3M#q||kT=c?4p3mtp2vl%O7PTe~^HoN-h-*veg+3P$v@Hr8qSH;iG zgKXn8uqiax6(1VY*j*>A5g4g|m|RvI?0?lBNajy?-XUcy`52D-i^<<#3r#Lmb@C7A ze^9;o{4eT|41%o}AONMknBL}RsjpT%X&n=1qOzt8hAuI?9D*fodSWMAH^4&dQ!9+oBEUQFD83Je;vQOI4krJG0A0z=PQ2o zWr{-jDItq}5VAVYKUxrotA7nF)V9jmSdxGEPbiP4LgT9A#YLG+bLekPX!R%)+Gk7VJ|Wn7oVGcy@GGyR z9`E!7{yb?ld2P#YhkKS9g?zDMf4n>MXPno%!ipZ@QH}WJlvzzJP>N85T|H80w?#eI zD=p?YZL~ubFUncsu;b&ppX+yAGvr7pVY|xzsRa3) zfTV#-tKu_NoHg!tMZdyIz)`HEhD2(ABxQLz;FI1_m(guMz+BS$0FM+yy(OWT3H<}9 zs->ujo&|R18Di9u71ydx$i(cfp+P#>_4!#8U67FuzreQxC(J@#219#p3iMa4zU7vTk3iR4AaN=3qUBxrWCP1=Il zB#CP*y>WQzZfep-^T{0vb5C90%{9lj)(Dxzr=DB7)t`KGdo3Hj3)--+-Ir?^69v#Y zvxmGZ_Gup{yhr~;KTrE3%@049as94+l|H~PP-6QBAbvLwwY z<$e$pVrStQf7rDZ2+0N3;Db^^N?Mx|&Z>~pb6mKzuBl@H#3-+i8y$t3i)4Z5vXc~~ zDe{!GB%?hX-~i;mar&od#0U*^v~u_Q65$e4FQ%Hx4s~wgg1lPxwAQs7MykJG_bwaO z+q)Jm9na6a>z|IFIlgc29MtX(km?XPqfI#%f8eq3!kNm+pZC+^zgoGZ9Dxyzho02T zojeB?1WooD%1pjcG9oZh{Iu#_CpNy^JuCVP71+(=(by?=O&&v);4-NA z5b-T*1Ag8ZIAP^&JM1z5eK6}M!&FVI#N%WPxI%2Zso-TQ>3#n=G^0}f-EWE#E!gq9~6*Am*x~#H+(_cHJe3F-kI4ckI@V!RJ=b$o8|#nU~WP%6s%- z*is3Nq@_$gS!$cpT5@(>Lgud3B3ri2tzKcAJZL3WXbMGhDQF+KJ99dIEVw@Cc46MN zXALo5w390_NOJjxq7lVoVtx>}oDGIV+-MRKv&b!$2&7KrbAtiIL!^QZYrv+?WO z!1q~ zsrK6@uOt5kzpAgy2$v~c+B{>Bi;!nVuFhbJ`tfNrH@Tj(p`N^PIu>2lgi#5PfaYm- z;x?!H9K^^kvsRPRJaUBl1~0%s_XCb<}D&2d9-;Za(M#-g{f+l{Z>LY=S#m+_e+Ao)jUdS)&9HcEUvm$cF} zf8Zo1MYy(E8FDuUv`NyF!D;&BO0nnpr^=9wrT!nr_03_Osv%?9k#e&o#Y)6SPQvz! zmaJH1uR9XPCX8OGPa27uXImPpZ*y|CdesY?<$QG4w?wr|5g^~ztinHF>-3jd3cMnP)#Vt9CdC=4p zbu0sqoYD!Hgikq}PjtWma>sASER#2D?MMSs%06elbEeBNh)@SxeZenKTZVHXa99cs z7E7Yn<*2o}*W(9jMRueKRYd_=v-IO&*JvAiiI|K=<5^4?F*UJ(Z*OZLY@)VdPy2(~ znPyVYss3c^`(G&)=brP;FFzUU!g{0m_cmA(pmW*Fe-ez1+tyMFJs%4vCBI%+!~Xs? zn#TAvuV2hg`R)^f3KYh9?|BfJAJgKs80HiQ1q}sNRAY3d$xpK+;7Q;A4!xxN-(oe) zneJ%XwUtpB+k>4zLY+fX9fTjF#hH&4qjnwY(=jOyb81;ch)p7+-2*3$3=2BcqiMw= z<(TF0wmB~xPLHgcLZA8lbz3}oQu=NT(;|9zW`Cbj>UhR^WF-I|{3|$dO|=9&NgRZV zdT$H5v+!Nu8Tva6xR2|Wk3ZGzy4ufme)BY1q@49v1d)YZIhntxInC;Xw^cvkvWM66 zOk0M$2)FX6LD5}=bXe2uN2Y)W!GD}e^b-!Kv!P#E=aYlmu#={KpV}Y?z7<#PJ#`O7 z4cwIZV(3QCS;^1Q#X3ZQ!)yBKq+%NP|FL!6;Z*-l=W z`OR)5dAwvUKy5iH+OD$VlIp8pPvz6^@9ac;`S?Z2_ETKlscnIHQJ4C4U!@*vd3mYv zuLVAjh}j(U=smkr2}Uh?x?s_Fu-lnkYBiPuTPB z?vuv6gUxje)kPjxw=SkXj|NdUJeA(Lnhk8-{j!qR-aLQEO{U_LYgC*sAUR1Bq^=Kw z*^Z>R)ZcXyPWI8GRw2TG%ht9EjW=duG-PBOU*uprZo$#-G{UG4HXK+LZ(~@-U|NqJ zsNc!~0mHK#Gk_3=r^H@|Q%aRbO_B-!wC{}rTJXiZryS;YnA1nuZN)o}gR*F{Yvbl= z1-rBuZ2MZpz#Z(C>tQ@GLap#U!vd+{vN((T>JT7MO5W$eSH`+hW6CM8k5%B5?DIo8 z!$z!+!lN=Unihh_FNQ2QR$|7hFaw%OwmD#RSl6eQTUb|Kth*#EA1R!#Rt8;l>bbkQ z@&yKs-gzkmadGhP2*@U+g9^I6QTpau$u~4k!gefTs~W0|l%phL6zo3KB`RlVxZndf z%;+1+r9H`Q2S+g`ncjaH8=HwPSs_e%0GP z^YImpIwUEAQuV;OJMJ7wB5V2JdOAO(95{OKRX$}_DB>FURZ*r5iM>dIg6N}vKXe2S z9Bg)*@)qrI>yXiE1O{~}Z19qlnR}(gjpuh3#BVc!hy&gV>Jb)pk!M^1U1B&t{?$C| z`Y=JCD2+O|LEGEO&qvKNU_0ZnnbhK;rw z)JFn6-32(8efXK47Vetoa8jkDeQRyQ0S^FpHO}WsiGS~F4 zA#3BXT=lXVrMA~JekP<)73bGc@z&@*X1>d|79NT_eKrUx*^=O31E!%da7H(5q)P-re# z;>M!krp-xrMIlBrcf17zY!wtMnjWmHTQR>hl?!?;I3~;tQN(Ol5^2<7qN?gBc2y?X zzFFPTi{tnp{x|96iO@i&n5_*7ySL{s-xJTJjU6>~D@2LDqUxlMiJFb>l`te$Dry=R z$s75-lN^C}JCj<;)mb}CUB{?My<@b@&m{-JIX*st>W8 z&AKVfAW`!E4XN-Br9z#3_YQ9;W-3KA?87{D4?G$+R7SVO0=x3!AQ8>=c;epv>0w*R z#-jI@&ET~mGzK+;76Mh$tOXw05CTu}VCZST7@NnpwvZ1APzl_u>&mA)vB0Zci!N+2 z=l}Ms%{R2Zwvvb~hUzNWEiI-_1FqHTB&3{4b+iu5q59WY znWL`FgK04DYZ;~go^a0skHeZ16v@0 z%1$~aZjX?;6GxoEf~dxW6~4(mWaO)msi1E&T6PF*chvvvq2!>E;b>^XaQ#=IOgKD+ zz0g#osKbIoH|(L~-&fC@VzTx&DXUz)2gt{o%|(lK{Mng{9iQ8wEAFQ|Ia=iC8iqiL z@%KvTF4AVY;5)*}H*=ltlBx9}ckhx$3fV?~%U)j~C610sE)7>VQP_Kwe&7iXofo*! z{^tc~*A^mz93J3S`e7mKaaad9<(6|58is{sNwCC~Vg|}=tH@p#CRxH${@6JlyWHLg zJ;R9i``cLwd6<)M)M^fcSgAa^ScMzp})R%y|XHXEVkSUPDpYLZ2VETE^rm1CTQH@ z-MI3_YV3sA1R>=QlvNwMrv+jS-gq z)mCI5Q^CSju-*J5xp>;4trmo}KO@_+% z-EHiG*Wxz0a^_!?3I|@NX^8tYhMX4&H6!!l=w5StGWHJpaS}3>V5U5VG8RuhRR^Es}cnbd9>4jAl zR8syTe%lX{HEd);KTcA}GYK}3DnjVc((f(=*~^`j7XP>w>Pjgabt}$kH$Nhb*7m-< zTzvY+_HSfpe*sn&R?hqq+7fEnAq>kuu{NcDWn$B2aPcx~2^N~Z{m{b)2!~$5vbVrx z8dSOQ(p}j2Xr<#guWk3s=yL4EkXBlxKHgS=v-sjIVOOtCW z(-W25Jd`X2(A#?59SyLc`zfT+r10N!T{g~xynPv@NOH`{K@sdK7g5J8QH9_wn+Rf! zzd5;7`yy2X^6@qNWY`ErJFGC#@Tkh*gY>eXvHp#FQ*&RiFf+BLVGH}hg6cjC4%O4r z`)BdV`=1&;FQ)O}DbGw^Y4iOM_>#RoZz6oso)^}3$45=!`UJs2)&B^_&yy|Bz+~js{bmdy$)FsZ z@=HLUC>Ex{WeYx&k&LULu?FaFp9~qf4<1{$A1|0!C4_#%TJrP?Jqt%Xc+kO9ksl=EQ79p5UKjZ&>=daF{(?6ZY zgX4xQYWgiyT%0z$E%0QAjeb^lOH_u>O&IeEQ+lJEdq`V(bL3eH~_66Bp3EJp*im97vq#4(js=deNK+Or;v@4nL z%1;HL2+ie}jTdPPS&IO3wWbgtN7mf~RRbmb#Fn0mZ52>PkUNb0oEk4^Z279xxUX$X zv#>^E(fq1z@ep5R|8sssB8_byWhH%?A+?#tBTk^r%F4a;(zW3Lu5h91W@g`5-}?#` zIs|DJMfgopEpX0{aDjtD|60v5UB%10q{+|K?Ji%2E$O(Ko|rM|W=avLXDH#6{gzpG zurOyz74>V~V3SbWpLtjX{CM?iJTzc!cL3UBl37cb`nW2hrZQt`=}s?ila%xHZUn$8 z5cY9aRc}U=#M}dWk1`=#0sW3PT?Sgg*{jN`zlV%!kL&|b34-N9C|UMPMv2A^y*xQ0 zx3M9s{ED6_mo1&&+{3DfCB1Hw*S=h!*f&v{nt2wG+%GC$rd}FUe_pB9}}25smr@&UnL#_oM8H5vm~? z6ltYINxu0Mi^Jn?SMg9u7&XqD-TdEt|bhZ3l^nmuMM_+Lke{vRP53KqnVE z*N+x>tP?#J9JpezdION?N*=&dxCWj0GRse$>d#*TxOEYH1mM>3IV)gq>Io6IWEjfT z(Xm;P{up7sNJv`4Y{AX7mkDR|4kG3^RO$@K#z~dD9lI@>$G%*}VHKT}{?5ZW%Ce*4 z_CLDW<^@zL{W!t4Dv7kK=(;U9HhoJL3u{(P04w$kll;dVtjcfL^-)VVg+M2wXb13x zYQ|R#8(FM>GcszsmEPIz2OcO5-G(Qx+<9sBKW_|BIp6Kh(HZdEsw|o(x33p6zQ7 zSsN+qvifLyM_9|8nD@sMugxc9i-{gSA?}qQ`_eq+n=X9o<8u)ya>{^Bm<(7%(<;?v zwa7G)3gfV<=*J`ReS9D)-HA{#C{TO)EHWGF5X*bsyqV?95S5{??641-6A!!Cn*_v- zV;Z^zhONQN=F*Ti1o4b8h7idy>~!AhvK{*8pT(wDi_xs!i^=ETrzxLnO6~GCy>aLu zdhCCoE?qtxSPe~_BV{uqQW4R1guY;*>t%0vv&s4*w2_g}{odAnF6fVP zLAQYJUOD3$9~S)0w`3n=H6z;Brsd&J-Hhog+?T*yhei6hJcTC@z=TUUmlP?zt6TXR z(lAln`sb@FoKZRlB+W!Gz-{gF;-PFPbgJ(fI5b_R6gp_30?O*YjHn4zuS)0rkI&43 z-v3m5IuZLj@a!>IaIz%KYM*^rLlHB0VpTuFq>oyY6s-w z2tpA5lIW0k6cojpfzF?|u)j*cj$8Dw%8PnC^!GsI?b$xS?Q&eis*x%rF&Uf^b_p{> z4SW~eUP^6+-ZDH3kMo)?P|E_VTm?wW%x}dg&QLM8StUVb5pkEJ>OgFUqh3Ev<%m>; zI8AVjhOpXAHH+P&J6#@?BGq%(gT1PPbhb`LE^dAFs|ZRzbp|&Ck4IAq*;2oeh!72- z8RIJAR%j6wUR4cK*G~zc*|H&-v@69}r$Azh#0C3j^`AMgvL=2@W5v~vh=RS!!^3^? zMVj>4m)9C;C68=YlzzJXWU28;{U3Z|sbVcBFE{;lV`luJGHXL}{)QQJ-1)?&oXBc$ z_p9yqPKrLUVQ7QUY|yJEriF25hjn+(v-Zae?tRZDs;h%zD&?g^>VMaIFMA3G{jMet z|6od}q7(XHdRi3kirrs1)-MaTS7atxVLOIU4PO5U|7ZETU7rj4twGm!f}K@~ zPU}gKJo5<^Rjc@H3tl$8`9t<`-6SVyRV6lTszd!9GG+q`$1vGV zn-TC3myoFRGQeETdoOOI3>ef4g|9Fs9+t9)58YxdcxKXQ6|Y0d7z4M3zjPcdL>;!xE>z%;QVq@F6WVks;w_yTe9mDJ z^SQH>;O19tVkt570#hC{V4@tx9)VsQEju3Xy2c1bpoAKqXP!+D3umL%A-4;T0}@OG zjAMY_DH{Q)Or0#8W*fW~KQ}&fFZlhJ=d!4=1}^lw&AxoQ?XN%BO%DqXVdD%0zpzu) zYKY~vtr3}H9c@k@czLKuz&sk-)Z`eq$xAV=ImMFA!Jn00;(cIIwRH1@6J}@n%H-f~ zL5${!*O+oo{w+=ybkhCk(=Mxjg)--Xz%_EpF}0pGPTcv;FVWqyXfC!FP~FFwZcbIK zTbBHy#`;OVV;}?i*gAt!!V*ro1H!TTTMVdrK_7B;eq8VYl?|4%w|(I?sV)c5>d)u*6<4h#G1Bd~{`3kE4kAAKnB zZ@noz?Sq)QJzSSt+IH%$R77JCzP7fUA1LseIht+2U##yAy z(cbqzi#-2{OCca~D8LY?&eOT3p-(aGt;QysJsA~HjX@EtJ^*6$X@;GXH(-i&u0ox2 zgBI&@Vi$vV9z0i33tc;2DvUk|olt6yUwHwY$O-5kNqZW!DEHg-U^0!pso`O?9q2`3 zBj>2E)4Yt){N1{%Lb?Cu^8aa?P2g}Kkg!HC?q0onNe}ZlZ4%-Ro}?y!0rL}@>;Z)u zLYXSBM=lG5mSLw<0t~k>ny1jzSsyfV$H6)XkAn3(&CG4)K(c5wm7ObXl@moEX3Dl) z8h6-X%v6kj$>9kQF@KtptsBcP4suSqMo2g%q#r*Q#D#3c6Wbg3(G>RSJ`5@tmfCwx z+6pR7N91MqM%H-M@*Gj>DoI({(GesUumF?kok0#~}RlR&!AQ1@xiiNE~hZ(>I~YINgt#BdUg zR~2af4hb}UB@#o5q^PGrhG_yVS?d_6UZMz==zK7c^dnXez%F5)`Gtj%Dj#N+UqJ&Q zJ!P-5twUDR=u$=ItR9acyym4ebMeNQsqIYj%vk(;5;S3Y?h9eDG@RK_GGl?U;(TvC z1Rl8g^;~c@%wu>mBhntp*+U||%cx_RdO)Ree%B|*=$Q(nv`zk##q)r$X4N_!ea1ZQ zg7}z&C)^(GKIEMtSeVq=itT0+K&yT&WUb@zpp&%m>9Obs3l(O;194*UOSBkzwtBoZ zcn7ZBS!XXFj_`JbZeuy&?UmnTCbQk{He1w4s}iwLjH5*N!>(JJnH??4z5qiKWqQ?{tYborY9y1~9&j|L=-QwdIaOZh#a0SK%!sJbhls3I+%+`O9t@C-m& zpjCl38#dyrIr?N(wd@~q{_f2u*$Soo@X4;2%kY1F!d&7b;SVa~kg@sQ02+g~^mrw9%$2Ae<{lzjHy!(Mks|s9TmxMdW%1BdIsRnmLi~_zHnocW&49O2 z!gLc#x@CgzEsE2}iP+t=00(dyzoa0_HbphuMPh1!AZ*5vpI|F71FA|k&oP>De+pk8 zmxCf>yZ3C?l{FD6rJ$>fVDlB#yr^yeRj_+qqVdkRhbaj(JVHY`%YzV`169TvGFia{gK#f@@RREP-|4?jI&Olx~T5Ag<+#G6F|iju7QuQ!L`aM zXw2PMAp%M(oDu;2p$>&_v26wFdiW`i4cdNbu9aY6p7w{sP%+2G85 z`#g;1<2+V8#|_)WwFJz7I5|A!-MbU&14Cm=S)C52Qb|#!J_Jp8QV4_?{^>WzgS|A7b>PDbDkU4BL0YfDcAc68U zx!5;`w%7-s&U!5BGafFmM$j@6M4RqCL2W*1TGUGoCjS^fB=v8%BGXXiU?)G~Jt4EN zBwZI-1}*3}?yukiFA+l@*ObCe7DhW&v8h{0Q9rhcOO**{i$&BwY0AYwY zi6Qg_zS^9H0*YaymorH}6zlPxWr@fMo{-FDl`C`MNm30P{it-=miOP>xr8=-0NRv1 zTPF>!iU5AbJ^y)wpyzQ73maFGoJiT42vkJ4V>ZKC&=Hh@5a5TD%x1wVv>L@{m1h!rW4Uhc|FIHzzYCA0hs-yIzg zhZGKGEY=CLE}n$*XIq};Z2LJ?+4>7c6ERdS+Pvzy6(B7tC!t1XsrPd_`~` zj9pWui6Am(h?0Zlz9Sp)eJ#qqOINS=0%|ZqpaP=aVJIG)aw@AlAAB%MfFc_nz_`-O zKUn?!!>21d0uN@Pq1DT|=%s`(pAQm}a7vHz_d_`W-m#PthrbCHzdl0!7Qcgk|2i$z zd{a$h^*!2I#p2oO6S^|>ywPAC!_001WmM)tg{Jsfa~c%OblFO;r=GP?q>m&iwHeK< zX#kONKF6*r4bTQW@GKxHse@`#`K;ryeo%kS3j z;zDFMc<{-5xcKbj*@`>%D_0tQHfq6%?&^Yyk5WQbB%pdPbBG24~-t$$ifjAD{fyh z(j6s$k#DH*WRx%V9$UBl{5-t92B5v8ePra&beiN3qahETFVWyYN$mwEV)Dup{w`J3 z+7xfBf<-znU0MD`+iy{Q8^ZlW2NMIr+SoVmDIC^%>wO^P1amK-y`E3kdgGHeehN@X z&@g8vtKHf0BI6$VkrXgMf&HP@(^@FwQujj%EU&$yCXT`YKaH6OZ}-{nJ*Cojc8BMV zWOK5m31zbMJL;=Xq!y>QXN%kq5cBqOaEKDraa-|a-V$67Q zYoe>w5%~)^(V~h^t*HaL*IwP$+7DG+ijaZadzw?+fP#I<_X*;q*2);1OB-A~8%^LI zop{~xSsD8kp-YOiQ=u_>H)wJj6%p>D*$+QL$e#jiXJ9Dd7lcAng$M(dc{;SRviw)} zk*&Rqk6r-DUPLT#`z??lm&Sw%jdyyiF0Drp>OZIRN1xh2&8;uyJU+6Dl(>INW%)c zK^Ybc@>C|>e*OEwg@hbSQgc=h~+DU z=zPj*eGNfnWNJ#U>h>3<2g3SVM0om1d)+je-bIsf4k->Iu~+2HDlf)e63!2bPX_>i z%Bib8u!lCF-es?Qju-rTs5DBwgZo`y8lr(O3t@|C5U@>)!vf%C*C_BjrDZXPeui%+ zcO0Nd{mHCB(H^KRHFNYmh+~Yv2YcX;oh0wOc%T6(t-)?A4;j79@1%lLs`JEPPzG~` zpxejXSEN~piw2#IW8uC%52n0}_^h<&X27V+Aj>Z0Jr%jp%=*1G=y^=T!t1{h$!S~; zkK6|M0xGD*i(&{>cs==MlQ0&iLvvkL_^E1xO zs`K=y=P0HbC8m^8qPQS6$!UTWL=nP}=Kh{87FGg$Ex^2VVani@(?8nf2!L=a_N6_?$aZtI+5H2#dcWxJSkkTjnIK>Gij<7&;^Q!1 z=7#RvAL~GLuO{tQaZgmvS0CnT8-nhH4ePsEQ4Z5iaMe=Yb!dq6amkM&bcz0m%7hZE zL_^{@t6afu`f(NOvlhF4T*_j=3ja4vLY4rTe{q5*7wUWN=dpNMb3bz)Ouswg#jKB5 z{d2cahQZ2hG3Hm8vL`ue&NeyXJO$@g+JV%_GPr|L`5783-=!u#YJa70Oi73U@S7u9 zzo+ic4-yIC$KO6!a15;CgG}4lVWGm!el+;6xyKR|{h#eXEWZcZ(0?)u<$o#2J=unG z-i0U)D&~?%5oObBCK?|TjLz>J>eC-Qq-OKo)9uI?S#EP4-0a1#;U}N(el~B8)4gyV zEE9N)IB50>sexf5gFhxjUW=e#v8aD44avFat;D}4V`8oh zOD;r&;6h%<68Fseh#Ws-_}p52Ub^Wrxp3Ar!An7U-3MY*c)%j1MuFmwcDj>W)653{ zWu950H^uJg$+!g&mkT&HW=sjC9KA}brJs9G4t$=c)A!zYM*ss9fNVaOSvHBF+hhX^ z!?@JKo_+ui!?XdM1MHvo_+~EF?mV~$>h=>yzbe|vRFa8QM>}vJH(H5y>v2xUt=CxR zEia`=4Rph-0Yen)p{BVi4N*-g6C`Gc@dbyDVYJ9q9mAovh?<)NWZH&F+`)c<>G@p? zb6t?9pRa0iZ?lP54?eXVQ?q|-e#31tnV$0Z3on*8muq>d=+!|q(8tS<@53oMobfG( zj0|q4x&!qF07~K`IH-?&V&pO=YeaEmS#BF6@>~*OjQDQN=1ZZx=Htkq$XhFK z4_Km`3QgjaMB+4*FRr%=Wdi)J8+|pWljDL@5ox&z*g^&{-IS~^ik0@~xLv-N*Sk=l zmToD=10D`Qn-TEQn|avkCmmOTzT~O)%*7v3wNW{}i}lLpe9_uBhgiQXg~g24C9wd< z-5_LLF>zlHGP0aCGflZ}z0*}T0P~$(^O*kL_hQsFBn9P6T%gI04Y@2kalU^KU^{19 zYmQpr$I86g-W@WkqXNpKqzwxrDGp38f4b+iUx^qAU7Lw~{Vk(h;Kx{x8Nm%k)X8@2 zTdyni@gX~z{C=*I5)^`5(a4coQoZa_urQO4UgMxSf4exlnT$NHS=uNtJ#_BUa;ho! z{4PZ8RIy7Y&0lzhH?~<)96TA(s8UR_nO@YNGFr=j{XmbgW)?>cOJ}$bhivgv%+_Eu*n^ z938{Y7+Tjq#c@ultDm@g>UB_*g_L>2;8@Rn3T*3vz>!`t;_8zyMztn#8yyG<3Y?J!Wr_Xnn4 zQ`Q`}Sbv3*^9%7uMv7awHSN4&x$fl{&boV*GG{?&&TM?(YmaXCJSDr0^=GP!n~$td zPfZx<{q}TUD9*mYRHEigY=OblSXl7mr)5r9oMJxL_u8_RjKLfKI$wt@#Ex!odM^5h zVqa|J8t_3CpkhjZ0~+bj4x4?y=J!%K6PQSV%}_-&tGwh=RwOp|TG$t>h_F_w6q^o# zdkN+N7_?=)crAR^Ga1|Nx3*j^rvJ%>JqnG$xeiSr^P%zjP!Q!kAKjq;&5vebnQ^q* zGcC?xc57&Pm1K!hsV@D)BKwR&QHdU>NBsc_WweBRpt_mj-qZXNPo{sL=KrBfk3k7` z3&7AAk)mD;JX{Q)@x|se;(MDx=r640w)U9i!q4m>164JI@LIG`=|}am`%`<^x;39> zJLTjubH z^zzMvsrUi_H#FI1&|xW?@$MqV`cfCZ`F~wNZy4O$_Q#H_gZ91-rx`bFYD?5a?e7~a zCT{AfHeKHc^q3E*YuXAG(5TyNC&H&15_tcNChM~1R&t+ftKIl%IOsZ$)RRD#A@+xSf+5@r_2`o}zR{f0aU19!0;tq5(C;xA= z*>0WUWo0}oi~CM!+c_E$eLr47G+9+!esQop>-)lMNV zqg9dXoEyaD!(qi&^?u11bnWBLT@HmT$ZEGc(ZMKh_j$jV{#%+n!R{VovpUa6k(SFL z@n``oV|ND$WwH_iu$P3jM==|~Atb*|qbXl$=D(4L`>Wnl zOUzX5TV1*vYQUBbC!hoMX+CpSw?*;iw@IMI8dc*|mGniT@6r=A zaGck2{KL}t>zn14X0P|bR7NbMYXwR*f$q|+Pq>;9U^qbSw}r|_C(Z|x89{=|{(d%M zJkb&riask)a!OqYSMJqZM|Tk9b6bX*5ES3l8R-2o-v0UbF&5-+`T#5y*!K+c=&yCH zG#EvZofG@dD2nXj(~W}K_K=|c)Ds8~Za&Fl?oQ*+E5Ldp(PqE}K^U92W@WtlzM6v@ zRDg_bfK^=@#3m;D7piycDtqG;vJaw)e1Dw({xC`<=~EXX0B)V0z@JdErM60;V48oq z@_p0h(o{bkjJw$L*DAygc)ky|6zxL|Pl>1%X3fEuO#RD4{rHpBMn^A}e>LB(Da83mg4FIip#+Y_7W>-U*=cIo=1MK~!-|nlnl4YIWd@Qrvu)VUl>6C4|n!_qPOBVTT;-Y~$_^^1; zxGV1d^~C`Pd$f=QPsAR@%Zm*2kQtu>#8DpS!4ooSHotKEM`rCkwxi@$%=bh8Sv=A-MTWyiFWm1ZIsWR(F7(^vqCLSa~IeP>f_6IKZz8vd`=x?j0EGj$CAAr9teW z!z?DkT{@$Y9^8drco1uEg^q8Tz6x#TlJ~H5S2u5ZoguQERZWz3$o&Kh=GQ(DaYZ-2 zyK@JR1pW+uzx=PH+uZlwZ!_()5Km7XpvcA7;zNAyD z4)ByOY+)_hHo&G6%kIm?0jK0nFRuVbQUd{F&`$w6x(>z4j=PG+5YAkDNx}E9X1c(} zOP9+ve~&nidcZGi8pI2ago0` zVcAW3+3QW)eNn*tM*IrVQn%4$-9q~_ZoTdPQl4VfJAm)>BX58GhJ{@*6bD^IaNwO( zy%IMt#sM`d!4a+T)b^023V1&B2)%eR%Rgt$&ivC1FgU0RN~0njE!tY?9549On&5wX zUM|_quRPwvCDNRjSjF|?F^p)ung&dcGDebU5i&A+rFLpyr~Dc;A^Q-p0pC741PACp z8ww&AB^(&%U;n%I?d7z-J5`}YMZAGYo|4vF)K}Bc>I@p7)rY2elLs$u}jxFt)9AjF0TM}Q)5N7NVy#Q`_U z;N2YogV|g#XSron{mJsBZP>wBR_wE1)8&>JVVBe^6~?dp?MTb5q!ReVmEb8j)-m&) zHqB#uqY2i(&K5mwP7)Y?pwX|S`3axynrT5|`v*4_AaAVO&%2n;tHG&tKbTO)Qa#p) zg_D27*IVgn1oeLgI?#Ru^9`DG;d8(xee-XjGZ%CI72v8?fU5+sFoFqdJT}vc_b_qq zV12zA$g76n*Fqj1V6IBsk+wmJc$}qbO!G6ih!x4}4!efT{=VnbSauH~6_{SA1ZKbz zL;Z3e=H3%NuV^X!NuZ}Q$~^n=a;8=-`4ouNCIF^%2dTdjsE-vm@tS_S%VE|7OwyXn zo`9kKf8fJd2 zLiWWyZF<{6t~Zvl+Y;_O1q{=0*V964vsNTD<~5_=R*LmFd^-@x@W`4}au77PUJ8`o zl{y&PH=!qdJSj{Ag>+7Qxyl#;RDSm8H*=v(zqChg9L)wlyB$eWuwolqL(W28g5M?j zoplXTD3j{!xv~7WhYfRYU;ua zRSiA4+aBOj@(?>nn(vQ*?mx9&{xSqNL;n#oDn}V}U9A|@6rkF@-1N8VFPnp|2Q&7? zpx6iBxG34ZK)LDwdl4KynrnS}w(ZA{_NNh|d0K;@DnU9o2}?KiXvUqG6Y;r)*9&wCRzGTOJLI<{RRa9Mr-0RU}0c44LAAY-M-NRxL1{N2CZ5rvOOK zQi#O?wsv1%jAH7Qqks$)^$ZE#A2z_c5;J)WOb8&Zp$7-7;3-c(yyG}t+^YZ8F@FC# z7WRZ>TjY<}>M)PJax7(Rj-fQg_p}M3Y?ITSXL$tkq)MdCejM(pL^owbB-1(FVLcA` zyl)D!uj?^7^2ivWlHc*wAdc{p=airUR-7_n3{VIjp2sH1WnY(0C`=8W!c$TWvL%@o zn0TTPqXebn2f@;bk}xhox&X!&eh)a6HN?3XAx-JLq_!G-H0^_>r3Nz;ns?Y9e$RF|dfuMB^?V%cy=(64ZJD{pnhm8gpZe z1jQx2f?U_@zRTSuBO!p`-lnZr5YfZT`wIQXvfj3=EyZDF93^{@phvyc1l-FZX!NKRL*DlE_AGre z=HQ`&;AiONxApdxVD+JExFL7*l!0}YX1wT5O*i365)1J;{K`FsvHZbzT{x_+WADd3 z3u0HJ6TpE3Q$LY3SD8CyZInY1>#vT?a~o%SM2jgxB;ueESH=Y!9@2J?C6dst9$1+N zC}j6DNnHaN^_luL*zb=&weJxsKI^BZjB6xEmAoZGMYwUHA8HZB?nT2|?T?n9FcUHyTV%zPiuH6xg(!{>a}yPYzXh!4b}<&$kSc-c zZEliT+2qU#gL3ITGC_1IR=r)TYDI|8Q!iF4>#+_lz)qiK=n;u;1V$fo7b?KHVT@^) zZk;a)cYt*_js5s^zCH+F&7HFD5I_x&72evPE*Cp5`?A(M<_7 z(G0$=l;6#TB!1`-$z|=h?qzuCJ*EA1fszlb_el-fAQj8&qy%`)o&3o+WbY3IR zSY|)Kul^y*>$aEpK}$dJr+%9LML^Kj!z%Q*V9`0b;-K2p z>s^QTKd54N&3T*epM1l)eS69~v~k?@dQ$*j+NKSK%8?_uBxX=JFHIOi7aD5{r4pbR z?-e{~(YVa6*V;=ma2&L>Xar}>uJG#Juz4B9$cYnq1FJ-&OcI_DUdaz9#!)GkxWPVE zqfnSm4cEO3r1oifT2yMkk(XHcnqY-;b6YMl(^c*6!<%q{ukzXO>%VR%800^ox02Nd1hs`(g=rxvRIL5Kb)b^q^=f#NZ{@ z=YH(hrpgqZ6maU8*kq0KXgC!_t3y#HL((I-lq$;GCQ5JP9Ev$6^lB26#RNec z6&)5oL&yqqOI}xe{g=h@AnT}@HHVB2u=3de15samiw}CUsLZ+9p=9?*1rhxkkpdq& z*4Vxf${7CDw!i7yaKmlOSERAnh6|l1(T$_kbg{+y0<&gTOwt1-MG6q>6Fs>>MH^1= zydx&v@|tZB*6ZyPepzjyw5&~*U_+X4nui;Ou3YacN1YR8^uk>jya_H>4*i2Cpu_)< z;Y(xKAUd;r@x>BS9$7?x7*k)9`L9E!yMC zcM{6m(J&orjnUTX%}!TgTu(7U>Sf<3eHeMph-5JWF7+VNPas>X>sZ}?n*R&UPsFaz z{q>qMSspvLERYHv-jjZw0Bu%%FjeaHxC-}dzHnHaN$NtH-%8deK=iY_g16JU#oHi< zW66usRnIZL@4QsAWVL$ThfN44f;Od%d>M033J zK(<5Ay?#v{^?Yg%*(e#+@%nlWqY-tScW)^`)aHEke_GeN?0r#R7w@pjku%Szv?F?s z6EhUl{4J%{B59Zc90)QM_x@+*ibVbVN09tFXrcGh$*IP4A!`yN^66tr$0cE=LyEp~ z1JMzT?_IC=M^YhGRS`f5mIN3?CeQ*`4eAoqO{6tnf;!-MDgv3BecQC*@~;MNQOf#- zcq1d#-iUSiE^6BS0k#@{$wAxcSEdYk%bk7Q{(Zws^~?@g4Jjk8f@zr6a3=f|9Ld3H zj~gViILdcq!O_(1szbV@1(b4)?-ZS_X756@jt0BH6|E$SVG?+MIyI4Sy4%k`n$YFZ zJFNYEto?`qw+0!>AwnkX2Vz1~pDtV4?l@Q(k;JR2fg_ci@VneE<%V|BIhuc{l=YC* ztPN(%-(=sGynCACa!l{|dQ=9fYv-oU6}|MRySKxyjw(VCvD*kOC@5UJ@%(kXx38dB zDT;BVhdi@CQwVqpY!voHDTTqaVv!>=g&-!SVoz_CprNCqmk8cAAy@WU8n z<`jdO+oK)0%84w22)02SB(svmhw^VvpIdSL{l4`S26~u|Lowri{*NFkNTruQVgg7{ zQ!tJf3-jYRKHci5bm{XLEMp~Z^SDOiFX8OH8BVXedUyMmby(NoKK*jNCMQhaR$0(T*PhKmNJY+yqdtNJ|0^|T1#E5vP11c1&EvSHe9(x6h=)q z>{QtWncdTGUFtq6*2@NXtG^xa6rd)M^;{1*0t>-B?#q$(B~jyWu4&+{+vbN>wV%O( zd2VJp>n4ro1Xy^5x~x0T7^I{uBb$6xcTAtx)GjOs_g3#MX9iar{w4|j3J7DZl?stG zl~Ud6D+9|p5kt1H4;e5~k7dCyC}-%*MAs8vIEx%?YKMQhG&{BBDbI4EplK2fgZNN1 z7^2YEN(RkI0ud6Na*?~9Ao-f!nZT_Bs1HzB+?NwCQis9xz<=oP>-yIifc{2`F@)I7 z5!XH4JH#WSo^KK!@_#(;YfOv(TCn{%GRxo-agVgpOU2FFCB5S2B@{k2_Uk*_^iCr2 zqYv4bheJ;=ZxH?6?G7kA4;P3|hBPspja8&kmZKDuW~ z4i$8tS!wNZx}b_Tz4!uVIm6iV?1=e&a$DxNY5N;0^Eu~6!fCM=V&h#;HQcaXzxPue zB~82-9D#jPZlrs$S34U;I2)mRq1 zoVVr?%+IIHXQtm2(Gxt%XP?m1yu+&x&ckp)>cX?_Dpj;z@+oHG@Jdbe6Rb;Bop+Q& zKGj;C0e(XG>K*m#5>hr5#>_kz)u@!mYjOS`-?~0M&Rdu_vxMD1i@u{C#`FvD1K6sHva-lq3;+`)MJqiWt>8x4)%ih44MfO5leC)RS~e=I@MA z%&+!<44IIS^G{2SehfW|#k?nK2<+>qp4!1*^IEs0Z5KK!?Y_f8?vhr{mCd1)z;U(C zX5+Ot;q3&<+VY&F;m-C8PUYyN{YC7zW ztiMUX(S~9_ev^!>iJUt29YLrQ$S1S)v2^KmkFAn0ev3VdFR{Z>W>N4SClWI$P+_KN zBdtZSueaX%dGO`*-c%11g$&p!V2f4{+oYRm43m3=6sGSwjn*6nN}fW6R(t0bU{0&X zqQ)IF&rGM>RpDTuv-pPq-U|?U`>E_UzeBsNCFO&?7_F6SEDu*+R?;j(yFgYLxhE&x zDbo-fRBhW97rZ$90}?TTIv|938xQs+<1Xm{4GE%v2$~@ z?a@!}9(YXVE*M7yM_O2u0Ixroy2l6YYvrZCxbBLEwbZrteCBs*4Vz~h;1u=bZL1PPjoyZkcQFXYp}t-?NP=a zQc63BF#XBy_`&&qghC+n&xBd3|*uPI8=F zd5-j#Q_c+}3JIk9>zes2W=e`KIAJNOJS5CBTb0bZjc8pqa=Y>WdOxaGpY-Y~d@a+H zJ&ev-bhVf`hXf-^YIZv)(}qN0zzRsdq$%-C2A0C)d6;gYJm&HnFc#-)Fb%G}2T#8kdAWdE7Yfk4Nz4`dnTH3&x2E5U|Ot6a+Q zsVybHj+t*1dhpE&4h6lRdZ`S){Yzn8eP9JX%TxF+XsieT@@13LwbD=z5En%(j9>F$ zao(my>S+_`CEME9J`RE{lb|@1kZqy--e07@%g5yCUEO$B=xBG*Js(+U`WeH*eRLI|frp zc>|ZbVIB{^1k?+Gx0O}Qgfhd6Ds}%CRqq{7_5c2lQ&t=to3fQ8d&}makiADZW{8Z; zNLeR@viC}n5s^*dC^M<7DA`2Hp6PoXUhm)S^ZV!ZPp{kUJUyR}$93JW`*pwWmpQ#i zMs>3tk26f}x|DyKof9iGn~R`${ges=7Uf%EG}Q{#m8knX zv;}1N$&nJ=l3B7fy04spoGIwqF5O%WzjbK0z7YJd^>%aM)b4Kf&dYtG3vD+JY~Om8 zfo~E=RE3U7zNdEwKGU)~P7pu5{&h@O9g$Y$}&tH}% zj1IWs9Zj~;Y)3*%z(lU)U+|S$#Xe7{<=<6buAaMD-n*uK#{#>cPw|GEOEEJhKZiP& zA9eFaShH((d$>)cn4l( zFI$PkrZI5z?ODZ9iT+6?yy$p=cEi{#SjBLL+=#W9q?oU=iZ+^u&?hZbqmQ@*C4qV4 ziSfwIZvE@!{ZyPNAGx8j;LJ}DMv<*7r`(|!!lKTocjX5^lTSpW4JLTxY%Hz%8D&ek zjXqHV%pXEz`6k{eY(DvRpulP8=TV-_pn)tbTFoaDk;-I8smQmpV@uv%BH|Em=`%Dv zey1{i#j?GP8KrOE9?OBF1ug@@)ye+e4aZ&fjZKAxbx2xOkjIC9+fF09;^VVVb(YBg zDhr1{jx#?8uegW-kv-L5A zXvJ`8eD5?wn5rf1y%bTi{?CaU3R7TLFFO?=yq>VGmG|z-9o%O+%OGjUgq9x>L}R5k zP{XH+9?v3Nvl+O*T=TI#rddLnSsW*$smrI&C+NN;(CFnJ#tnal>YLwuD2Dod7x~E4 zbr8v~u}uCS*P_N#=eZRRT#KvE;pEZE1qVuMeo|0N((q*&$R&{9eADrfsMO*;CX9)* zuPkLMmc!i7es!*SBAl$WbVbKDvZ|a^?YehlkQ3c*`6Eo|Un3#?2i`$p%v32o>?w=3 z6|j@A_swCiMoAkN(?%qMuBJIn+1wc(6^^LEgT7 zQ{43x>3j^u-`qxn9lNzkGFG7ipF)o?xfpP^5;$-e&8sc4l1$zf4nKvwb?3p&Qz$8- z7?KY4&iH}#)iRgotG2C`4_WD%(6F81Pm}_XB)`-csM+Hl7}76yB1Ev=EvKGv=m+6y zBg10a!gbwhyNU-IJOyAtb#1EfDs{N2msuMWMXU9ofra&W3m+=JDM$rw%w3s%t1@uD zEAV2`ckGDSm7oJhzXP6C+dA;{YqcIDmtJvgldbm@G?uc#+lx9IN-~o*_t7o&46^Df z*Zd1t4eI-&%9P$@^X7ph~p= zZ5(_VJH6WU8}lOVrNi8fTdCIq3jJTX{dQz%5)?3N7*(RkZYbt$ zF?#YHqj(dO0UqXcPA^Yd(U(sh`n$FdS<$U{RDEa)qSXYB5B`i~%(8R3xD;`n!1RmU z*Pg^7*{?$JihPlrFErFPwso!vb5fJ=MCfAqptofxTgEN&ypQTtOQ!=zymWg4F?H~o z>EE^PZkbi9a<`47n}n>|G3R=w^KDbP>`EooOBtVu5_ciT&(&+%v)=aCH1s4<#BM(3 ze0Lf{Qiho3!$)0}5smdpvjcO6SCy@eg%#%e z3kKaAQ`Am(l*v`7RO~Rpl#k2(_&3 zeZtKn`eH-BQC|atMo$EN?J4TllS zqGycgNQy!5^pzRelpN5R+-JY?@QyVF+J8Lj%9Txd&ppi(=f2()8tl)M1U?X~fRxA`h2t^NB*^Fc8tRc( z@8hb6?wN-@pZhZ!qG$6lpw6WlpXbe$mh)fwu~?q!3Ju1ZJEK;VLskmcCp_N{K0otOlE8SJyZd!}%}DxAoU)yAWG7f!`mNUOv( zpCdXDkfhYt0Ep&=11TO&;Ak+#fCxrgnj{Q)@qK<5QaYr^5O-qxuWI)j`L5@Iq$la> zkY&-fihBj;>v>*GtIyM;KeMw|Afq9g6ulTtVRgGo$Sia7a6m(WYxP-FVvov|>b-SO z{xpk5ah)6PXOwGBBdMtM3GmSaHy03iXzeqa4oJ<-O4Gj(aE{gm|Gm=h=^@B5>sw_f z!JnRG2Uz2GUxXTWHmehTJ~)5qH?oJ}u2Z{>X{EDkkQBt$qH@=5Q{p8 zV(>IBv@;m3j5CU%zkhg9yG0)3fMgj|>0@AjVAjWh&mw}o5NqGvq_~B|Jt-*!S6LAKF(#kuh9K`V<}Q2B*qXZZ6z4p=ZB5&+9T$*x+38fCBC=f9Hz^jZ2YL zotATPsDW(OiALYU?cUNhNv$=Di&Rtj9eQcmeP7<2-A%WaxnhguvtZh0EO>ay`@p=G zI-UG?qgC8fa610+n6o+j!UMIwZR0YD;y{6mR2Y6xf7#)w2KvJw$3pHs`TVGF&*%8U zq=wCXm(DO1{FY=CL9Lz|0<9^nD!*E7pAjh?WgV#XNGo4Lw#tB{HDEcXV?5~1aPj5j zkJp*bc&1gIr&hd}{p1)6csBa^-($y?1e)GqxUB*lF%_^iBY2L3WG(ONjs512E*~uX zwbT@Tj21=VQOjc-15Njd7YcRBR0d9hP$jq*YK>Y()foWED|HJ z=45$<8Y<7psBjb>6)EOE&w&BKV|qMcF+4)O0;8;tiY#QPm`@mVIXhKeQe2IjSkmx0 ze%=H2vsJ?yDmV4a)p7o!DD^9ZaXXbqPJ$*n+17{F6_e7RzLIiQ)zkA0U8&h^3X*9n z{#uw{zFopn_NgdcOw!rRYM<|SePu88rrSqP$=74_<0Bee$SHZJ0P3~K?|cT&sPAKR ztJ7SUY)=tGkTef8Eejb%QT}xj-pTkU8Z$QhlSKX$6++qalTAI>mpB~EG8g*72!5Nd zZ6d{wRX!{AWe|?Q#UWsz`>lKNV&X#7$n1|b@RCbxS~lJMXndHFyZELkf+_Nk1tLbR z=98Q0l?@(GOiQH8;xqJRl0h~_Y;gjW7`nEF6k-p2U+YhR_9*$G(O<$=otFK8(otB4 zR`xN6mwK@r4su3Ib$~!1C&v;b+{vG!fUkH_`3r0bHRgGRH|t(5z0AsOT~hL4b*1q^ zlQ<(xYF;&Ng>1V&(5aw`x0nohDoS?FauX@QG#%<#%a_J-LC|D~j=f#U2qmQwP4Y~L z(;4e_Fz)7Vq(iAWL|FH;!KWdbHnx(wXQD9HFN3!#a0i@a3e za*_T+i}v~fOF~jFlY)U9W=n-DAqRgi3$ycaOU_=%Qz1InD3z9T&-1dIJ8dM7#I`%5 zSdACu5_PyUIXfB-`7pl$bCvQbY^!K{o<4c&#|Pp_m7=xsy@gX z)vQ80rvh6$PMndjQTrw#u+p;2{_w}qcOT2Hx-qnh# zqmCZkx!f^Dj=55UPM_u=@sv6SI3!5*{TILME{r7IwdI#9Yvebvqm=7Vauk-izyk0_ zz=Km|mQ`<$>k;moxd;W0dQ@{c|#fPX99D7~!Y2qn{{Mk{(xlj7zy>{O;rTDSC+5T~j`u9FNj| zg$yl^zgX}Bn2MsLv8+bpokk4`EF-6|dN=T44^um8} zH^*v2)XuX}omIB2voD0;J5xb`Fv@R;l31oVuZXDNrJrMpizI9ni2TSJd$dJ}sJAD#Uj zVdsojU=Ya|V|=!Xp0Et%A(lixsB0dRVr0B8G>pu?R|+lvg6W#g{0GL01bQDcpH^@p zW9T25#yp)=hI#)~%8J(Grp@+z%Z>o~?+(`XwuvpF;l!)-0l(ZPQEq;vw=8?F^Ir#@ z?~ABW&-?&ktSx_xHTgKY*IbShY0F$7<@WHie@BTrJLiuT#|=7*MO)VJQNvB40knHT zlx8n_JMl>$pX0Jsz~b-*&fz5dUB}-Huoi(B5u2_GeT`-kTY81G{nFwx=Yp@p*CvRB zKzgoWrB=;L!25#lPo?vsWsS#&Cgpk3>{#bVi_1~#O->hoH%~AZ6@PBezuj2S$!h60 zT61QKYoN{Z_E~9H#%M-!<^nRZd4XF*#2jA$yx;bl;{0b6*Wq(u%byR z92`pGC_Em?1>YYZ{Z?fjljIni#v1 zQQDQl|E%Bdqiv(}x#)U}(}NEhdVaHDtz~fp^c&e|7TdGC;-9uAEN(5#Ms`qS{IfQ8 ztAA#5K2pl<+RL!do6norvD0<8@TgXA87YR22DsEQvn9V67va4FnFMfk+)SpV2dISQ5g5=@&IrXie=kRvo0LsPZ1Y#m zJ^S>fFC~l=fzJtUaLnA7ElCvnrff0j zW$U>bzgMRL6wKakT#JOtNP_U!YqYRRgDFwfu(!015vv*{6|W*exuIGS2SS;diXT!j z@q*y-^Tk)f^NlymyoRY}q(@p5=ZmuNX*_Q_BZg>G@Nw_u=Jxw7KJCFg?e{^O1|UIZ zZCbJhf{7*a40-3@dsCK{kso_1gF_L6`<{%I6*26n$>ZAO!GJ4Ku%WM`kxa#|1! zG-L;TU>ZG*u(rMVH@nE6`y@V5Y! zdqw8%?c2L4y>}{W+Qt3XhZib^W0iN5M{A<2`#%N*cu*FCO6JD=s{wXw$Z>J<)nI*~pe+F?@lQA(IZM#ddak>hB3mU7jQH9$sE zF=n3kbp!<{p82vH`YkW~%R=bFtaoc&w1QvA{;b-Y0|~&cC8& zKxrR~iDV(d)z71^Z6y43@81a}f!>k@X73aZl^p`iEWOkRs!>Pj*?mPIcV}Um@P7F^>zU|yQTii4)}V9ZXJjX2h;HusQi6R(T9_FoeZLs- zEJ}}-gG{-sD2nA`$9HzFw9b_XYv2|_FTja#a)J>n00TC78gl_7-Ifk@yl6n&*RCIT zmTo|mqwlW@jyXQ~nJuk*$?XP>hhvLpoch^|wGeypS+4{XA@kDK~hSK@t4KJCFe4FU*U zp#4q%SP~)2Ovdrr=faH-(Vbr`xqkkw0jkwI%Wra14&MyG%epc(TjE=Rg`53Fa}JFJCdlAC(_X39gtxI z$6R*Xl&{UpU-{r8?X|xEY4t<-0AK$A+(CUk#qyn0fpns*!I$PPccToqwlJCmF=#A| zvKaducAC;vrsNhAfg%0J^pJ2v|CE{`jn*m zL8ZH*4A0}sw@MG-3+b||XTO#ELgS_Kn^^q#1 zc7%){-eQJc zSYC=gKEs?zPuDA5+b}Q@@>F-fGNwTzBrz3^%crT+C~GO>Nd@VEk(Scr)B1J{l+wSR z7kSGQsLKYPmQ5|Ml&*4WMT8}&G^OS&vtv_ITJSj+3q`&w_qD4R*(d6on;JpukFDG> z@rI*tlN~Wa*@xVEo+X?Fs>NMe9Fa&XE-s}XFbY11A7M1dNsO!4`8$9)(Tt5OhZPXd z^c&R*T)r8BrKNh|!{`9Q;DF8e3;Wp4H6{wymanG7R|Enp$8`(_?jB0Fm}YJN;K&|3 zr?QgSwW2^$)vJ68_1Q5mUL)#~!_loF`v%7Qqu~^QAzqee_C=+4_OWAay2)tL)q`Tp zB{aCMl`-II{4bVWS3wM{uO1MmI474oz5yP-MJD zGHbzGB-DP)@A1AaaWtR5BpSn1qko33UWmlA&!o6hMU3@6%kjCrzMpTrkp?X56VHTO zfARn$;%@PDiT)ikC4dI^o6iqE*QQ*xlnEK18xmr+yXtL8!;|~%EX6wkQbe>+hT9ia zo`P%FPz>KM)oz!TR2h)XKcnEsLw&@g%k#3dy1-2a`U_mHXyn2L<)y0GQE;i-)w_@j z>Lph{`XdwMx{sk0_dnEf!r%%_yxp3it1F#Nx%48cf%ND$D6Tx$M~Pm3MH%OiD2<6( zw3+%6vum=%!jhWO^XSQ!S^I(=*Ey>T*-<5gm^`X#PY-s*vRsRyj~y;YUe<7BvH3K+ zZ>!VV*s*^|D43>a-@Q|rx`rbW^t=PJh7@LcyqEC7UFRUFrp&gyA)2}*qx4G%3^{{e z``m{5PfBf_??JzR+8~tetD?SAIK+CZJe!|iae52PH<(l4F%^81Nq#MJ<6HaCXB~w{ zIoR7os+3EG=-}j(m(dXvdhf~g@GwU1X?LCTqpTdujYpyK1>!yQ3>p4(mxrT0MpORm z@zZPsrEL9iUIkxiyjB6Q>YYG`9na_|*~>Z6Q5)s!&kPuDu5HZNN&7d$zwqN6v9hzpXKCai9XNlm*5R>2wW`wo86&!d}n|f=nkkxseG|LYS5sh`PO)6 zd2b>2Uy=3xg`zJ%_ISgPQ9otr!AnQl0}YC?m+*I`F@5Q{`uJ;qxM|1%m(ING2t39#{-1tG}o+c z)urQT=73D;@$P@tpuw6l3iZ^8epyCo5}MHh=6kPhmV6Gg7>Sz%SD&g=q0Dj*@6xX) z5-O;av)zYmW4wUgq9wohcr1d@`~8s&K8le>4W`K_&%1;GJ1B9fDB`tCe$?2%*AjOh z^zb=aR0k4qL?R3v*&u#kOWtIDSo(oeov8B1mK(CV4EGsD`|`Yi>6!oNri0Q&1BQ$7 zsP(UXmYvM|qrcizP5A$kv1RwGJ0`K|WjPT09S-Zj&JUuH2gss@DnMgYzFH8!rdt3f z;-ZD=TqF;Q9fV>6qB=wz50o$;LJ7mbj$F|m`<1x;q40YMu*A zrkAeFD*c9*NUKplWE>A@2by47{m;L-=e3@5uzWg*lB%hz`}H^C+%)<4WYg%~b06LK zBAs1{4=P+QkW`g8$SI;2<#{=NC`0bweW5|AXM=gI7qzzlIrkJw^0SwDNo>2!GuCht zZjk|Wf~@iB<2d>nheQ%yrvs*+8pCO>bg_Xr_8t((tlY)ZO%(<4xolPjBRT$+^mbXo z#x>@XANIrvK%EnE*_=xcbM zFBK1+xFAxUW~J5HK@5B~IzcK@3A4d?p3>aoIAE>a9C~j_SPS)i>^#_e7>(Ap59`8` zD{7^C%j`#=_LeNzPz%}pMo`NxhhzlK8;E4hT%zcUBOk{o)wR?*r*+BNG|kiZbp~Zz z4kbA&JZ_3%4>mM!<$cCmINcCy6>92ry&E4*Bex<_XOH9rBt(4;kUP(?N~F|3$uD^E zthl>DJETku4i>Y1yK(cpOBl4> zozU$&BC|gXF=m(%>S+0`De#k(qUn*32V@b?2Hag8layONFiNy06>-O$QY`c8m{pOH zBfu}nG?OeheAmtdG9Lmh1J;X_GpxriLXRb_6AxIA&JKuLyt6EJsYEGOX~0geXDy!c z)(Yx(aQ#|(EC4BJNWl0RX6*PRu@J5#u$Jk zw6wzKTP~CH%Z*AU|kU%44Sw4oS&4I3m%vo z68}aNTc-Q`wopnZK7q2(-*|O|@*nJq`SQ40){Wh;6jP=!DEIB!Vhl~F-@_u zbGot7J-4F!Uj!j_?*(GrpRp^XR;3YK6HA5lOGt9 z@2ezKfI9?upTH;Us?OZWEOjH#-Z*B7&be%?RjU+L$Zgr&ia?tE#@>wjCdr$~Te)}~ z=SLO8r~UUrzp))UrOMw@4U9X3Y!ktJo0r8;QQ{ML`SmF+6S^D{Z66Z4u9cU>prFFi z!1rQ&+xM;^sr!3tF4td$cmFM;5c-NEieR+?3iPm*gb_h7k4H)gZt?&a&%}VJC>VKf z>Tr|aWKETsOV(#Ged0O=!}eD$1%?M4jX%df0rvQ$&m8-vwn)m^+N}YSUVOBfC;^Sz zVl@I?zTkBPI%PeKqFX`K?O2lMnK*JZltN|r2$Xj~%fL>wP%@I+aEJ#h*> z&UH!rKGR@ZTk0_iu8B{T8U0p_VU9W^uOQw!t62i)1{;FZ*EE@)i-J(@auUils;QVb zV|N;5#A<+``ErdCS4WLnFmR}HQ&?QkOUA_ykTL*F$3)oX}{MTQh6W237?prekH{X#?RDHR;S z`>*yRuJP!hIZzs^gbDGarb+Jz=7-1FeT1{?;E8^%l?Xl47!f^7kw+=ll9H6?CE;DU~qv2NU>k5H)fmym`Y$S%yw-sIZgt9@Mvq19k4- z0+03L(=NQXS3U%(4d2yOk7*l;tsHrYhk8mGz4?Tli&(=@r##6;` zJmf!iba||t+@ZXN-OLXIPFEkOwY!Q?Z3BOKL+VP14p{PHEk{Dzd>G+yu&FqOlfUkE zc9){Jx&mqBuVf7~eOk9pE8N=n9NUTPw2`f}+W)9$!V*yI5sGOzC}t1+GH+YD^6Q05 zqJFGm+0czZ%o-kK8`*7M_;o+93BP$@O4w}od(5I`x8a4`z(riiW9EFmxIEDYsu~#W|?AX?( zkCg0aeR*CRCe*oJy=zAA+?lZSKJ}@r?G8EXu{d-}N5r9hU*|E0fK%T_TJK4=$zhXA z_6vmw3dFyX`mw_tPxq$+nT%4}Fy?Bf5&nnrA-?{1j>}w~KVLrO$Nue&Binj+HdLxo zmbI4712Kr^^bR_$t}Xbj-0%ry5FWswPt{D##ODIL+_ept8)6&(bc8dW0P{mfE=nkG)vo4D(ZasUCOQXoGQ=^5_?OPBfcFwj%yNEg)!~bjEj;2DmC&yRv%>(aZt1GJ4aqh zRpL9z{-lb0=KQ~lqBd;%|94Iq6Dd{Gnn;d7_Cr zz8%?gqg`Tnoh`h+&VlLpU}i8?#0YcJ1eNP+8L6>IVg(fY9>PWm;2a!aT5KgO84FGrIuYwHWC~v0 ztcsaQT^>3&e9l6*TcF@}% zsiu^&dR*UrC~W&^Nq7esJAai3WlnGU(NS;#XM0L*#&xvM(Hau6UD3PcDUzMVekDsz zY@%sq=IBPDC>BYb{*91>LdL!Hd2UGHW&#ZD_+(w)x4gmYV`^?n2tp>t_iN=Ab<}}r z@=iq8h*H31pd+;vNwk5id39Cxu3-oO}tI zdI%_!Y18p-ez5PSCk1GP@7|1TYry!+_QY>fP)@@9x$ zoVF0QaQ^FD?;o^MFC&fvs7s#_8@yp@{pBvQsd@|{X+M5sI~%ohVH7oc2K%aSUN1fdSZMP<+z&V288OI zI(6{M^zJF*_#3x~em8X%_FR0qCHna$<%|e+^;hRRY7Zq$@XqBsw$I{8d)d*FMoe@A z4u*K>95Cdoe#p9-y{0XPV>yK9&jOcCNcWgSdw1v&qUz-_zfj!pHlqcCm#B#nc-|>K z9kO(AEqWr+d-@#s$k49G{(RNYL$o#mJ?8A())^%qYFEG8J@*DQ2N7pCe9u7e{X~)3f>4Fe0-*iF(S$rklVpe<9rUUQ7Ro6qEmGNTj&}1=hN#MKg%2G zKc2QGX`DK8AP3tgh6&rU)<;%`vb2&-pKFGHuw$P&28AJ;P?vlme}=}Bo+^y~V?k7f zAp_k@X=E=j%I3@AtVivFhY$2!Mp3C|eI0ptVaQvyNu5w=B0Px_Nzqu}lkX(zKjGKq z?CS!NV=a2z$g7ujsKb)*US+iW9nbwmqL*W++o#(fOh;<3S+lzh+Q*TW8xE&q1YtJ-xd9ukrW7Y9LMVo*O$e1c6%Jn~ z2D$fMM+qMUo$1FN@92!jKkrA7gK7Q z%8?zPwCv=3g690J<6#7Y>QY41|3JOq<Tc)G?*m`-D)2bDO^F7>cOnqT!AWnXJ5x~&Q|mVZ zrQBq#xLXj)HJxM-xuTjAI3jBsx*tU~*_}Um0(`Gq6{l#qNSC3|HjjqkZ+(OvSmbF! z*j#FhiF*IL`Q9A99=XgH5!cI3>M~Mg9Y{v>OC#od{S9|wixNWy-{Zoh=nj<{6ZTnj zlyh;2DMEHdnX-+~@l8MSydpsu@(eO;Vj6Zg$(`jn`B7o`QT-Yfz2N?1F%PBDW_L`Y zB(N*jXHUWGPVKAai}hPADPTm*lFYEOTRu9v+^X4}>^{-te#e$e5#I8)8C4<(93Y3D zmgEa@&yQ00x1lgO+W;M03j^SG_TOKCs^ISf<;bq=SC>zIH2_|A_bCdP2LvJ74!BlK zz5tL}R;T8jJiylNrvv=oz2J{{$=JVbwDR>V%%o@D6<}{K(}y9i`GFokIDe zq9GdmDszeX-~CgCNoHxezG(B!oR=aoOu$Q@k?BHSB+fjAwZLKw=#FWG*KWfG0sA9p zRJUk`Xrzfp#gn^Z-@rpynQGa}*o(9Q4*yOTV#SGfe{pXxpIJ6Ny|kV?P}zO-*RJVO zk7^K2by^)B6@ECgIl(=?Fw+C0*B@;kbNJ?izWmMZbd0oIxSxhI$u48K zGlrTmoRQ?!TIl^Qq_FFR#JCkWP(po761bmMYLnX#y(wnWt;q&!e2Uv^>1rc+FTmE! zeNf*B=H#~iRq~PVvMf57noj)shrs*W!}=X@-=EiR)$>z!te&FjIE8_?@WHzo-+|aw zE_Gu-6>_!bx<@kWci754SCDVy9zR!soo%JBer@Cc7U2#@k>dtRMrQag!7hGf++ zzhB@hm$8#k>ZU@aCxPpHX_RE4vl~chXX8-0QgZbn{)ZI@CLC!&x}Aj=YX|fSU|X%WWqcoRCIx9>NsV#G9o6|0;j?xe>OLa&k1iMIacU# zK)4?jbPIk6{r3PY(XRU7H~D0A?4da=&=!837Q=Ogq=Q1FTM)V8n;O_z``8j z(j4g^L)>K!us#*6Ed6vbrHD`~V0t9YnU?nq-a zb<~}hGobzc(s5&8?r^|%^Y-t)L?w)TW`p|*l$gc9055qJ3E2)S$HqRd%RDaBX@_z@ z_`oDtTtl>vn2A!(x8_O9BIm`dC>06lQJ7@LXR%$n^J|INmEm^JfQKyuVPqIXr=3_< z=l8yy#$IJlX=Pz4sc$q@-%jBiN2((#XB%RP0@Kb3T-+4$ysoD~o_70_M(40)wi2c= z57{IXf$;pv=*ofc)CkfzM)`U~x1C_pb4AjGssy%x($j7Om=i`r0}1iBJZ#r{;kids zwBU4o@VyJ6cJl5udH;kN+jV0ss@$F27_&zG-nJZV=0W#&^$Ztuy*K#Wf3Jh`@h145 zsy4!1qQi6%x}NuTaN=0F7qR&Ltlo5tfTZuic9wom zcM+3@%YzqnC_}t_)>oq&dc1VAlRd9|i(aPdu*GzkM<4VyrzUG0DEH!WBBK@)wgt4? z_555hif+%N{$#$84dZi!9)t6xvX)A4}Mi}Qb>@Wd)OB@{OW-8 zD zX&+n*5**>a@wR=TsoIWn`&HzD1Wk~21Y5YXTuguhQ2q``Idul@Dg>G#aCEvM>+Xl8 ze5U~iB92C9B4A<^&p~QzNWNVwvsKkU>wMhz&`bLK-VY`@X_cuq?z{l}9LZTQW=SYM z&&wNgE$IlH*l?s>4%-`UWtpnXrK)HC*clWC%`1?giL_OnsILd zo?-o@`=gh%pzhnR(p=g=`V*iDtS7*^r=2oQ!c`O?ak@TscXs7=@Ua$!|Izg4i945NcPt=az4YuS-`OzH}iR-&(2+wgTbJxk6^?1q)4<5Zb+_oMLN)PPIUL?r#F{M zcj@#HUMkj3F-=4LOlXpaeSl8>+6()g!jKdqrYqg4wCJm)FT1TXnf<-Iuh|6?@z&*e@xJ!)c{YAZg@ZF|U3hOjGQv5yPB z(M^T&)=I8UH(2t8;s`j2bqgDMQO59k*-*@-^CDpg;j9sNG*(_v_#`&b&5Jg!N6vIW z0}@sv3tSEYbRKocKX!Lz<SUS;%^r$hUL5YfA4+v z;%gX6)6ee!@jr)=G?|H;kjdDCF4z;+RJv=x@LZ>2tF&PBPpHiQHpDg8Km-t%CpOgvh z{4pBgsXfEIJO{gXa2c3;t*fGWs>TK@0SOr9!A^LLZ4r}hW!BGByj>Rx7UG83va@7^ z?zQ|3AaGooU!jP+Lathqc~*9RXx8al%ikn=cif*ZEcC+E>U*bm1MMBtXhpaDs1~(p zXF9*jxOpnj=!MQCaKydJ$E%Yc$=|;laFQO2&&buwq?hJ_C-RB#2}reBQ=ymlO+`$` z8q1#2QFB1M@kYWT*Md5Ol>drLwO^`(@Nc3x#0{b{hoUjqp81U(wG8Dq?g<7zabKyx zJ%NN%gL4ye+`_sg1f7Qh_CTK?qaOCz-NHEq-6;O=w zm;f6)1(^SyerfgDT23KoB&`2hS?K{!%)jK$Iq7OYy375R4EAEu-1lEzZo%VJ<`2UV z+6$+Wvd}Bg_OcHf%M$1oMDgeJ5@6=5gMsP2j8p2+OyV4%=5$#!mM8a8|52Xg<&5Hq z^iuYj;DA4QFoD-?kZF`r+dT8?fq9N8Oeu8%i8i|QT4p5fyJvu^yXk?7On;4_2ox$H5m-S7OB@yuZhrk1HE$Eh>Z;O4Ve_u= zHQ#@~+uCR^CIeFd5I$9=E}b zAZ^KZMmA`)$zQmGDuLg`%^zK0TfkagnAC+8L3L&+of~zHMGek7h~>OUM`vV%0BJqe(H<6^oBWS-@C6w;lzy}~3?noiDd=TVX0v7Yw9!CJIqWR& z5Z~DT?v{$yH&r%au8Rqq)2~HuzGexUj9gM{}?_9uJ-Fn zF-J-nBMZt?N8@G$PowzC5V&ypPP}M0f72so!AqO+^S^)CCk{P^hx$;*v1q{`iduvw z{c~YHr_o#g-QCWv*T-{hTc99?xvIl9T$5($oTPs?K)M($A)Z?2S5}_Gqk5Dr`&H+{ z7x&&<$?eT1=2LYQ}FL~aP?^if` zy7|gbg$-Cq)CK-5dA>s3(LKjUoZQ(g)A4m!j!y*ZJK#`$Cz+Lk@m+hlAymsjhv;-A zOkTbQ-Z<$!#+b7KM{Xyzd$EJY6(=#+(D2H^e+yhbQorHqR@y3{60QwbyNPCa*X|ut zSBtt7IWFQRXGD8PH{QyIbDkAJJ%89|mGQ7>u5?HmIhC3I`ltywN^sd%w|Pr;9@m%I3lg94>kWb9a=-w@Be!O!;KBUP?&Vs z!mZcN1K;!;ntF6{F!@}n2uA7(GFJk&kCOZ5o_nI87?ueXsD>GtZK94cL!tvgN%5=K z4cjHPt4u!%y%oU($)%Q7TpFT0BX{V1Z*`!ku1>!Cx_OC|3my?iTowI%W*3Yrr&HJ+ zH7TC_^@eko+&V3bbk}Z!F&=5aduLd`F6vEZ4-F}kL3KfG)`znkn)AFIH3sc0Y%{Z=CRcisXn{F zvg)dxG1r?G(|<`;zM(DxTbim=)5M9eG|+vI)XeGateJaCjeZEHu455bqHumQWVWH= z7Kw^40BHDz>ja6Y{_3FbfG7p`nWf)|m#H6uCNHIS-#MqLgzNCC4KgWn*~*Qh24qbI zXH!L%`c;F}JCQ16%D50c{tBiMgS0qhIaLX0QqpK8`xLl3A|NGQ2pk>p2CA&4@L(zLJ!@FqHwy8lQj%pl41PAO# zD|<`h71J?NIm?0MzuTLssiX(ydtUtVl95{x z|7zPi^_V}S4BFC-ZRA?^tSpNO90ImNpT9Glq@{vbvv((4z|)5V#SgE!iJAOI2yhKv z)IKHzavIV~zZ{v0s9mgJDlj#7mv{ST*7xfy2@-hE>WB*?v@4%Frk|LP&vDARd0x*? zj^3+-D)5#Bv~B2QXLIF*vjdESj;CfoCR({e{|12OpEe+VZfS4&Ui)e>WH}dawD~wD zF^Vg|Uu~Y(D&!@8wyDSOc5_tLugBlQ97pa+TaVJQqY*Pn4UWR8mImuKDqjMg`{YO6 zW_Dv#rWehcQiUR%2hFaCxDR+oz+Q-E{CLt6$8}6y&5fN0_mH1AE1q1qRCv|{^md#w zY`QC?O!Zo7HENp?TzlY63Uy{wvSLBNzZ>8d^!-Me`(X3sr8fn!fq0rOfb!_8GaXxG zQ#IXt_ZFbS(@BsVysARPL2()%p^PoV#m@S)HZ8|D|D9#8cMh;nbP@I&G(6N8gOVQ~ z7T^8f!v7AgzQPs${UA};4SM)~C|d+u^#IwTG|=+Rm;s<&^SS4-nbpj#uB5)VQbB$@ zdH=o=#!l=l?E|E+fh1i52N79j`zVmjG@BtO0h`w0+^ta&T%8ZQqhI}}ct|?d;+#xv zxd}%e4J#dv#e9~0xz$_fKk8Zh*MnNY?Bx)PxbGL0mm%1nc+C#N7k}a-sqD2l9j7v$ zNJ29C&|=^|*=hw%>=JgY9uEi9pu&V8D#UZQq!AFoHi)2lgK$1cK=LIOjwEL=qy@&B zis44JaJ~ktDW*R=EM9v~_M+s(1?uk)tQ?|oO=e3&@2q5ggV^mbjX|GrYj zg{w1Om*9`nVeO!kBj)g})A!8ooFGC@eam3_kWjE(v0afF)~;X4VJ?@dx%sD)!TJy) z`JvOlm0Tl@JGuBDE|GXzqQG#bY4bipSeHml{~w2&XS52bGw})bjP}|u_|#@e2tvQR z+2G>$;&?j!8W&Ozbjq*NP)i=5dm!fB?*iK|yRFErcc*90QC$kO!AzYOAwH$`?Qb0P zg}y(WQht?2Ze^gcS^3gcBQ2*FKaxp=#8U`m!|BZ~<`ROSLa1i-xIZ~Au4w1T?0p8D zKg1yE`;Bxlk^qhjjg_l%8%V(!-vtJ=Z%9z3H}s@Z%;C%pZ&buLXO`~HKAe7b)@CxL z@C$wTuPg^YuHO$(k8mhuB0v41tHx12iqbZWPFw6#0?@+?dk|j;L+LFL0DJK%Z}!Q- zf-@ZJ(z9_{qhC{#Ty5g$CFnW=z3E_IPBD0-w-0L&%Ix(X9K#nc0|_>HnF(g*)-5l& z)is~%)G9U@Uv&Y2DBBA$V}VS#Q4@m?=-RZA*qo~ZnYOp`Ie7J=x~NlN$x^K0n$X?L zbbNv&o|`F=vJnsaW{|m|2b4~QG%U%#v;TVzm!#Ch$DlO*Z`LH>4OIRbcJ3AHP-->A ze0Cb%PyX9)WXWu#F63!ihyJ1SskE&gWL7CJRx0H1sBi;{{GN+n#R;1bRo&tjUWWJ| zZ2#hg_-1JFh)U}zoJe8=1h>feG&qNG$&Q0SYQo{a`1USXv?RbpSj6PgFquJw?4p>H z84w2NEIU<_} zuTl1GWs;s?MLmw@M7RhzXL{}bGi9l5VR)}&r2K0qG02al2jokoRIc`v*Zy`#L^s3~Es$)YK9p7?^8g{)qe| zOfpCi!^iuW$@bifP{iZY-rM@h|6z$Vk4woTqA*VK$*n>kntxCXyqTYP(y#u3LXeVT zQXb0i&q~{gqOPuV7TK{mnSAc=hO_SvgUb3Sq8K`(i%wspCf2dyz83$~1P=$LpOJ%0Q*jgBzrD`+tSKcRbba8$aGAkt4fok)1|`El?j^ z`~qftpLqow|DJPvWA`3zz$^)TrHzv3=+Bw{@ad6qjtVJ=l@mf*L4r|o1dR6D=4=~gp5yxc!M}8o?4pAjK88AEtCD$T(reIs35?;iGFwj$cDKQ>7*42XSERhYbXCr zYo)UZ6RcCZYP&wQV!Gbk&pjw-M{7cN0mYVPa}FL?DhGq1evS-~6F!Ffb^Le9#C1zF z=VTNkfmG0B=KsN}v07Po&Xnr~N1SBvE<49X+yX>E0@v>Cf+;5+ko;e-D>e8#v z`;xkrsF;G~U8#t0Vpw%wr$|CbZnUcavfD?XjZDTl1#SX_GCJhG+b{G@bzn&cj5?5dAmdD66wCu!MCb7cw-~qhQdq(%9#|^aS7vnnX5|U#=!n4E zA$%sh8y1l6P*za{$N%tm*Hc??s^XFU+2*-+WyMD6Aha$? zN{_phAbOwA1b^Cx_m=(-(fD>IgFPq;A8Y?MA-q!4T1jFE4->flWbQT?-r`QupNE}o z&IUWXINiJW`%Uug7GCoHx~E$#ARK@hB9~yU7k9NFU>kj5O|_TFEt#1rr+>BI_4>S< z)Bl*(aCV6t4A_<(zn!_8lnF<^RiexcqzP8_%so?0aRLoI#Q|Q@;yxs~62T-}@))48 zwRZK#^4QKrcWw90{I3P-0)+cjSzX_jdPzv@?TR{3;M&fMIrS^sQg)YEFPVq=7Pvnv?kn@0J`|afce#%q6(b(bdS^mANl+U zo+LtLe+Er^1~(^0&L9o4B2CejfTMvR_z)tX1!0cCCnD6+-R&gsTA54a7BK+m(H4XE zp*IB+l;W`7a&_(AciLn-2T2|idkhWF|5(n%!BnKj;kdiTj7?siH(GS;siv2wGkU zKcPy2y^Z!a_(YIK=Sxf8#@*f`f4=R#D<0->{83T71k}B;QR8uB>d#<*85`@h3&&-} zKItYjyru684Or%!EE({@MO2f9%bxxEuKnp18TzAWQl5a~mk{kI41clWI;jq@E!!pgeX;XU7Tx(}oRO#7CFE z_R9XKzw+zT^EEYv*FlSKP}*_K%Hnk652-;Q@t9(f3}6kT2&(!^AE4L#4IL=)=Qp`W zfQRjS8us`k*wT|#=K%C1Ix#l+;i@?9Y4Q6HpT;ob=ZU6zgvmQDKYBEMS=Z~*^K6d{ zF(C2b`oictdc3Jvb?we05PgHo;NtI+J`+lM2N$;k zc1DHSg)CTW(RZ^~8;57i#`!^T*oY2rcJyhVcy?cA3B^PimY!AF0y-7K3APA2j1cxi z8X+3RU#nb=0W*HHBURtTJhqpD>&93PD2kw#zBw#h;8zcg!Po48)ePgDwxN`5KM?QiNc0dk$zseLH)OTICIrjpXc7msswQ#`a2e0{vUbL+5z#M$FgeZczw z!oPNlfB_qZ!ChyFyy}O*Z}5mpp3Jp#!93&slOGE}8~Iz+uWyx6Rl!mg^}72-&as#ZL4BhTQXYU)#Yg#B8HzqRDie-O`+gJ-ckDU8GWQJ@3}_@?{vaxQ*3aeh(P` zo_*?Y`u1q2A*Ndoo5pxLjThr`F;^KY>SK#Z+|WpOa$vdhy{4{f1L;2eohx)hfMrLR zliX#KE^}XBzT&yo=FMa@<5cwkM1z(*qIGpYk3cR@rw!J$mPgpLb5Cw38Y%@hgZOPj zZy|;lFlVYLszp~wONCoX@hlF=BstY|YmSG9Kj=nRjPiIm>s<) zWN~~#r_vh=gRv` zoFKN={xI~)wb07=5E8A&u62eC7dq&vyhPERY!_U4_N5o+HXrh3a4b8rz)3a z%9Gp$=9Xy5ySpBViSIC#uCjUz`PoUwAeSXq#2wi#xjZBG0;)^=*oWC^0 zY-m zuyd%-mh+Z?9tFM)J&*-b7|LN86iac37?5MKnwkn{!g))H@&nt`_95*0x z&@)FWAd;)9S=s1^2Tlua@8o3ey7*#$7+d`%*v}X!6(KTx5J+Ii*JE*UI92Z}{pmpag`wU8tsBSM7w?vxf7%@s1E7Y4p@?fgvc`3%GqA`=Q}9 z!pP?{vHRFox94-sx$PTDgb{|{&S=W>MWu4RZZK=M4a-(rL)J2*w@r5x_#Ac~OEwHr z4*6&amX6*>0MeB(&sUs>GMh8DI*m6$I>3Ybhi{6%J-NQiWWw`bOGzJO)-7Pd0Xgm? z!=Y=2_Z{3-PQ&q`wt3Wfmsq# z%-~T5HHn_vp#!W(1u29Gce_Q3h4JX&v zOdoBbwx08DaSQb(g=%||L*50_4sH+2*RDVisj$5Pk+Ho-@6uz|H92`4{%Kdn9L3J- z1U%iN;vrL$G&nRabL*TB0lLN!z}15RnsUP+V?%$HVVC-X*Sy-=R{ATiT|IsH-cN?C zm}-lhxmfZ~G%QBfw?e^JxQCVmU%QHv2#`in3rHX|dC|`-RYeDtBGFfY7lb{}e*Aam z7C`h8i2VT8e=s{2)=M?PuISsgcd5{ z)+Nx^czMo$`%sVV0H(xyqXC^2u#~qzU*;}Od~>TA^fa{_BsF&*`89H)aB^qy+Pg4> zGzO~|dY|^P7_nV#EEVR{Tgw&wnRP4L20FFRH)c%bU5HnT?p#j$cua@T*RTEt zYjTXvtPi<_)_Smaqa1`T?;P0w*46a547SI;IX~(q-+rwcaqO=o88`50iwaPRI)>Kj z?14b`?*R2I1mg58KcKs|}Crp{hP)Q&ycot7s$N*6w+k1OpfwKpHxdBHoy zbcl{Ewc8bW=2CHY3$IQGQLT9z?+Ia_Y%6-~^X|T{IR{Z_QPs2#X)GXehe@){qmR_0 z@6v$%s}$&iUW{rZiu$^l;N8Knf4*`dEg1)&ZK5w#zLbQBR(HME?2Qc7^|*fB(cMS` z2I1d(db%5A&4C5i=X}Bjr<|JXUHsi(Bklpgs9T|5)s{;Vf6kc7y3V-{MqRgG7XMr{ z56xw}CH}<8L`cczyC~`7KOo(Rp_khocK>TBuWZlRg4Hba^pCvh1en}74v!@MU~&#a z*}*lfS=2K=e$&mvE-WvXdPrtp+X}-N!@xyX@PLTSopqoN_u@zAwp;Fd-F~;ATLtxMJA}F zQp(f>$(kKdsm-}TllFTm)5-d=mq|oohp!Bj!d^aiIxFZLN3)AVN;~BhDYS$UUZKBA zmBtqR1+}iLc5Q;SZ{%Hf?z`O<6!(VY7u%t&DyW(L;32d}ib5X~WvzTgjExak#h<6# z56n5Z)usBN$0Rp^2I|@?sBxgJ`Ab=zxmeu&#&PoeXQ^EZIGPpNhu`xFO>q;cNAKdZ zb0db*W?4yCeVu6*zxEbFs*g-l7A=B)g-j*Tm>>Ez=J_L1ruWB49Cj)FO$$>d^|EL4 z9mZ3kgxfcH6OzJ&y5qfoBq=a8#DbjX!LT1-)LM8M*W;D0E^EKmwTRjzK2aV`V%1Wr zP%>w^oH1+IVoDz~&7}EI?K<2l(M%4^q|nQ-`7vNskK%7d2@$NFpX!-aKLKlsy(AwB z1~-^DU3b#Klm|1wAnrE8YHxXB`M>v~42Z)+iiV)#G0cp~<{zMy;zTS*Wh$$tJl-fs z1_$#(KV}Bxajlr5@w>2l*Qh5FTc3OJ70>}8vv$^D3E}YkG~Br~3=&k$*9a@v8ELd_v*6R4(65r}*VD2oZ zxQZW3fm~OJxJB!dVjS{?RTIu>WDg_{{CT3eV-`)4w}Jl}*r?G;Ftwn33Q4@`dAb0J zW{7gikD*Wo63_2Kd2~?hD#OyGGc7~?;WOT~d}GmcaEDxC_LnEWq~a^z8M0C~E)7{G zq(Eut1J>md`&r-7;-G~6njMSUg*ht=)M@%NRDr6#rH{;Q^Ba1F}6WBsi(P%_>eq zh$RJY@Y7#^*Wr1=JLRZ~!=N4`l-h*MTZoi>*iw;A0oqOve7W&3MC)P!9cHun?9a%6 zTrqw5laGuVLq2w|wJEikOt)p9WCtCVXcUwC=#0I zNb($TCFJTW;Tsur!8;+xW^K3!aBEP)e{A|w{vLpy9*jVCvtB6Cmd+Wl>z+3pTz+rl zfV+95r?-gWj5qD#X#d5I%l(*BulQf8u4G*xFSUAh7GHG~GFY|CxSE^*2sY?482uFX zX#USD^|-FPjyp9jPn^5@7HOGha(oOb=%=-?mM9R| zfRc-@T_FKVzj<2`(_+_tz-El*&*CF4u?Z)tA;9#FrI8GbRm`Ib39S*`793bvZqTbrq(ih z_9gwWSy_gxszn*hO|J^HVoL4FiEQ8O8X-&1k_~w4(?){yk^03%&kE!49;cT&oLO}8 zB$#$$z^#x%O;^~k^=1QSeJA6i$-{23aAy1J{Zr|V3p5Xu1?f{%cRV_x5yE}Od%eWB zJ$kgyS>l6cjb(pTiaE*5kR16vL;@-(Y|17~A8%iR^`SF?vbwqF@y;PkwjR7@K5%0t zBJ~9bKrUQYRmKAf4{9uviywG;EhzZHz)a>FdJ@KD+K@+GiB9uyvRkNV$ZtFPfWN$c zPjlD?Y}ecf-lu6bAjxGTdn#*-NXBC%9sQTG))OJ_%-R4Fxhwdia`kCFHVkR*CaS>f z)HWeAo{Ps@t0NZd1Wp4S!u5d7s8e@8G&N@K%4tA1qvB&nWGbGjDW zrpOo@HC7Kxok89+15{sW@Vyg;`Ags%3}6su>&3d}kOxFlwDI|XGu?FNp+yDLbC2n= zO}1?Cw@PW{xzrl?!mzV5fcXZB@kOTMPUgA7M6p`myC9iIFlUg=BbYN8P}>VmwAxb7 zFHAfBbRa%s*I99+zv_#vq_kUS>A+|naw_}zYZv;|v+9iS-;AL(wNqj)AEVnjm(*kH zx0;SwXo0#3=5s;0*Hy}?r6Da6nf#8-Dfp@l@(||}dpnM}mI|KXK11jlYv9nBhPXo+U{4Oj zo)slwVrx+ZQ?rA~DiB}k95&-_7f$-^WLNSJP6j}64o-jPIl~~zD6ieg{O!vy{JG3L z!-lyfDX4wr3iLrd(^m(~-S(A$FWK=xcq+~d*%8Hoa%_v2PL|tktbIR%texbC$iOv{U3XBK9{!I&2}zF6%7Hdt;(@tF(!7J38GrR4b(fw7NAT#|r{LT-;OCX7#_T4CwIuzMsGWy{nJ&CVvo1A-k1a@|Vh;cE9<2k5Q z5PtjXCLSf70qcKet*9WhmompYt3hxUtE+_89cSQHC^&e-_({9wN>;siwPrtNv*iJ$ zzgTo+6bW2G2^@8(en-f_--sz~7fbR=`rB}CnF^Sg6e_B~!FmDIcK9FJM!&{Szks$A zVla4uWZBI3H4wQU0bDf2Buby;t-Nxl9`2W@_ZP?nuS3aeh_qYhOTL%+p$jkVd_2^i zXvcqR&shjqaA+&3FF}eu`Rz!H?Utk8^K#Gpsc*4-LyXHRB_ez9UcD+Id$C1g`U5ez zudiLTRj;pPVabs!+6(0?a*i&H`{`l1eF3a4Z^2)V-C^39GPdB)!QnpxhWE$G<*^To z{kyij2taSQ^m%z`zI0Z9-t>0MRCXha?B)gADD-A-*(Ink2oU$i1WXb{Ihp%_v}Uz*@im)7^ir;)bVc zwxKan69?cF!hE3D8;_$>FTktXi+wJwTIuPs@N0d#o#07Kt9JL)gYNtvHiIR;U86qE z{Z-LShmX;Pf&6pM0DvkS8Y?V-@u9nHl~KxQ%qvQ?P4NXEK#f+3ml9%H!9Z3#_<0(c z?RZQ99zY%O*52`H1OyiFdrAo>BMZ3oKOJOw7bwVy&ss7C?+_{{Z5y4-aujoY#cJ`s z-{@?hpMnbW@@uSV3VVzsGp6OmvknBZ$isJcl9~VFuig+j0e^k+=?q^<+6)zj5Ofl8 z5v{OR^2~-rQSpmocD&17Vpp`^-YKMfffn3gaA3|pwZn3RKqQKd{lfZ?wroz(NLqi2 znzlUO_wgJ0&l^-<)R_R2=ltmw-K|_Lv@FaB?qI4a;DIhYv5;|bxij)@wSOg(FxQ(e zZgw*x{ZQ^9-q79lKdoS~N`7+^T!Kfi^hLf<$E8zv9qrki1wO{Dl%bN`_m_)){Z57riC zyD`1gtObSN!Pd4jfcXSpEQm@?4v_-NK2DSW`&L2ddA!omWo>37)hkZYa^u2UCz$Z3 zguaJbsZQ?jB8P^HxWVxP&D|ib6R3T6Br-e82w``0`Mn(Yxg^!$1N?xURg3S@kMacp zJDTnH#9ZTAV~(W9Kx0CtX?P@xGE2yoBqD}$<&FBeL}#$3ue5bmvfiVzqY89Y+ijh{ zy+I+Jy{SYabQj#PvpoiA(iU&A;NP{xEf>md@h-3ef=+%20{CI+%fEFXEwB}ZwESLp z0w4P4kM3S33jr7)S;FwN{xuAxP*ya`CK!g&8FfnT-|rLADcv|Z)I0dgL1*ivS+>m~ za2jdLjZ3S0?&I*6T^HM+fJu>=*`@@*jEXrOyV1~rxMGF43d45e1a%(kOLSnKaOUi6 z-56yO5yG|a+~CcB75)vv_zm9BYa-4`pE{(|S}}p|iMCQO#rI14ufsc>LEd5B_n(~S z7}f?bJE2^qY9Qe}v<*rH#Xxi`(Nfxo{#*CWZ}Fe&-sl=%>)DHHA>!7YW(t#`EmylX z#RM|{a6_=e*`NhHaG>%*?N(!pT?vODT9R5A%llRCx&CM;)#SRh+Ur^wI0iBN>Jy=q zc_02Up3*oxmxG3g){l3=ZtF9MaKqf?m#i*5i&1i@LeQZAzee|Irm-ayt3(1HKN9v# z*mB7fF+OJHM5WuzPiH@T2eTJ)Lmj>b0BTfXMw;AuSKR34VPYHTglIcN@wV4?|4 zvG&$>^88)s`OAR31@#)E*v5b;08sL$133I_XMVd^zek__M7k|=)J_i@1j<%(B6Ex1 z>Cpds#suG}IA~Fzv<0C&xCeXKflz*3E2YZvnv{`8p36*jt>CHK^z_UyiEP{xmQJ%= zZFZns2a%Y>f3-&l+mHrX2|s2%Ixt(0+9<+cfM0JB)?~M#}>2#pw(aaT0 z--cFyD@4Z@W|}&j*EH41;!d6zJs9X;3kLce*8pab+j)%&8D5o z3aSklW>m>+oWqIt5u#UOqrgE5`uM`b(koVmFaPWc5g(9_U*LG@OvIQNPyhpcL{R)- zDlof6ZBNP^*1}p3dZC^{8mvyjSnx?2YumFhP~sb*j$*5M;SU}KETgn*e?cG_eRa0ssZZGPQlIaoMryOD`sgzw(^n=eLP3@Ukve zet^ukWIwBDyEMTP{NVblyqPM#r&}parrkCY_bc&@3q222sP7RXA?*1#puJ7=&|QIa zo3(eRcN?F)>l2X3^piSt_b2=>7rw3^^Qn0*$Az^b`=;H=?{-PGr&wPH6Chm<7rX%; zGNGg+$Of0Rq?Btg{F?3LgFl!k%+B?vqc5+*THgnxcc&^TDivc6Dk5Nkmnai}a(i7A z?L4HugYW!!PjBb8uMw{P|mY`1H+&Pg|ig&@}ilyX8BkX5QZkEJHXb)yGGT;xIF*uG+ew$u2A# z<%cHS&;z;y&{;Co6AUgEj$^?vklF!Z9Op^E_YcbdiK0AifUqDlX8!kl0>BOXve%wI zeQG+V=j{4b>|*qF>9vWMdli^zD8ndyBKZ79=vix}}5$bZFY~um;TO0kG zlgxi|{!e+Q8vn1e21JH4nTO3m)K;R)Oav%+qM#$UJJJlKm$t}s#k-4Jc?@t5smUw$ zDeVtX#MS7MYM=x$`)+I0Om_@w{&|bH-XCqyBe}zwmf>cE4z+l5gfc^!8`s}@uhwY zkfv+ip=_Q;*3H=3m)`!oO9VdN2_{_JCC&b3@aIBE?oNhU6z{$T8P7mg{NWIiquM$7 zP1x2>J-U2(z;^Bybt}a{i6HF>6X#f=<#uTpHgT~ZxT|R~yP@(eqYoQO+ykf5e6J8z zjF;^K0`Tp$p8gMfugqR2!{pSsL_-(1JD5KL!q-aT$IYfbDG11>{Ne=6P6e|=n2PB_ z4aPxaX8@1lYb-AK2*_UA6r3T%jdU?;Fxp6Oj&LWQ)pz$8pN^p?jTad_Wd>P|sQPaD z_)HJorLhv7sAGTAtRL?QTazMzzjW_HDl#XM<8~O4C>RiMeW7m$G82rP0NZ{^S@&A7 z?PnU)1>%lMy{{?yCBYr^l>sT8X@r8i zziSbLp#*u7@C#;P^&NFiB4iN(-_ZcmBtb> z1rjO77o17$hpK2;AO>lZNj1uzXeXZ=D3AJxQS_z@VzRj)7HXIZ8%6IJw48NX-cNO` zTen0#k9D>B?d^u3Kw&3dKbM?w6KT{va_K<_A2XL;X&Mf_4|6bnq zbR`Io|2+hT!yP63FN2uE%G8c@hJ-Z&!Ayu4!28*wAe10Qzk+nT?}pBwXppadk^IqE z#!gLE!!HUAEuaEj?6B2jRiWk)`K2h&EQyS7tSyg9MRKfc0ef)c(D!C~?&BJuwcy25w+pEvm zMh;?%>j-`Y50o|F=5W38_hcXhGmC#VSoIY;42FpR?SE%3K0hs8WdC)Q5g13Bk7U-& zTKG7$+9eYbW{08%mJ+G(&G+eiP&VRmP8d!hogXwq^eJz4-vTB&o5{20z;180zc+(? z=s*on?-7nPRF0=WcmN2?idIV!h*i5ki_A9t{(||ro<`%DME;k`NK3rsc~euPB+z(c z!=c?rwu_8Deuzi2V?TyMuY%@Jxkc4}N+RLPa5M%J= zM*96#M<6WF2;Gjf#sQEXLDwy3>Q3 zj+@?pGWg^TwI?!TJXyDq;UR_pwMEnETndM=ZVN1@14>I?**U6R+-<}?B^Y(TeJ{bA zNd`uKD@B4OIYZXIp*Tc}5NMT6hhW0j^!jgFMWFuXQBY8YyfS&x+N;a)oC*?xZZO(` zz8Js;suI-@ak%Oj{bD7zjL(+WwH(G-QpGCfAL>mpja^DvLILMrkkUbkI(r5?W>|iY zG@HDX6=SzGS8)jpC4 zRyN!}X;jFN+bfSJIg1)#CPi!~dOjHhMUl9$SJAYXzW%EkE*Ckn66J^!;#T)c}E zFo_E0+`w=Z>Mz|*0FD;;G;e!HQM)h9pr(4%$UAywvSwi9!Z}8VG*_qrmi6J<@ujf1 z;4=|V5A4|{6b02k>+*iq4F^TlUjwpPn0&x3#fLkE)TMkvKED7lt6pp~MFhtKvZc3H zvs|i<4Jhw>kh~YawfGj#C*Xo>vFS1i0G)zZ)8{}@BYIMb0CM@6yn}zPxOj%9^S4a6 z(#LoNg%GX`b-sCqDu1kYqcup`fgms%3r#GE)yesKNuOsB!koWQoB|uQ$cBSpV6>+Q zlBHJ{X_rf%4q|m8Q@1fRst0qqphi`>cU<(NA4EiDoqwk9P7^ax)DYP;@ zH>OqU^n*skH^C)*2qLf{f#0ZNtRWX%Is#aqiR8xC1y&0~_qAp1Z3$ zVyjnDhb#ZV3z)N{z`BKhLHca(K$mKMMOxdjZ>4=&f72y~qE%3`M$3!QYUwfFKC%fn$tz5du9vY_m1T-}Qmf4bgmf^Ofhi zwI>5WdY;DQv1*u}kN>iyZ0^4KYeT2dQW~7cZq8{^Ac^@mVtTaFRvEioIF<^f*>cai zKGaV-fS96!=>Jx;DkCX3xZ-Dyrh(QJjK1VaG!7rCrfhR2fm=p_zA5F~ZZen!Eqi@q zVs$kJ*eaUjvOfiRfa~TqUmXqi$pLzAGxvj>TNTa88s+^sd_kv>KCpO#Tx@&z|1Ldi zasYT}#o>RP2LB{=3`I8Z`e3pu`0|DTln!&{mSC>jNPE?^QroXY7{&{M z5VYjcKF20(apODyW>w*w6)ofd<{fvc!<-PgbHXDSoOHC8D`yV_=T)<-+qkK zW#qHzPuRr7mNEF?9~k`2E93Wc0F$#?&bPQ&A*{I%09()IViSa( zne0l7NwNlY_urJ4f6=Tvsv3WiDV3RK`;#w|4@Deol{Ah&i>G53r`Vk(&sP;fuX%$S zB&jt>o3>}@aqQpS`7FpHGWAsl&jV#FhX{QW2Sf@EAAkoY`;wZshDA#Z0?Zv-D(-PL z?RsU9=J@zH*gOq`R7HZ%wwv3KFaw(SlVa3?RMEsVm>G;=9m;hO+AMkwU74nT7m|~U ze8=O8hw4aV?wcCuU;SVStvfJp#XPKInv~jpOXHPqgdU3RKKUiA0f?~JY_Bu|VRqo! z+D)q*nz4xHz-+;PcDfq!uIcgrt!|tz#Ug zR-7{s$u>-$)sfO*?6OV+|0GZB>LJlHy{&Q-|EzB|A^~mQ5+3gf1`Stws6r1Pxdjxr zDs{psP*l8S!Ey>6=AmZK!sPUI7d7~R{2e!+UTWGov6&$Zw6`8i>tH)^hD8 z7k|9{E7okT31SpSa7S>>f%q3sOZ+7%@Wk9xcdl7BusWRrpyYbRJ0qAu^+_C?^pGg? z{$!Fcd!sToee(}q5}*9w;#88Bq&^u8zw1hRWlUy{h&y4cEPDaVL;oGIOVYkdRKo?t z3hJYYwkS|K+ai4(|Hhn8s>WcnXL<@+a2JK29oy&wQq`Ua565t7v@C>?P% zGo(o3^K$;PhTtI|{F#3@dqB7!XqTWkHN`Y-&u>>~HTho0{>Xc#QcBiHJc$LX=*wDy zKXn|#&_{~H=Uz6QZTTl)t1l`Nz<(+0A(sW*b22bssFi9As8Vbeb@=)Y>1b?A)r%vM zL*i}F1|G?0ww4c&E}BR=U-+jcl2-}+Q8y4cCZ|E*qf)sgN0AGU$kHv-=sAO$P^-3z!QO2Ho?D2)LvJGkE#$N1sf`9q!8bp4sTw zc5}R6lHM)s=;6}N!^_e~_x6xWe)jTAe5;e!QZ0HrYBP_&R z2BB`Ki;35|Ee`PpdwsbQs`&c&RS%^v&)(&K@%sGiUEC84hwpt%F#PubqjASDws14& zC0;IIw~rz&)(njeJemNQmQNstp^RkgNi(Tw9DeklpcvmXt{ek4N4BAPCWa6i7K|N% z8AB{TbM&5>*i@F*_+FQZ);Bs%;r;V;K90rOU5?2a;pD&c(j3i36?2amG>Bt6D?9O~ z#EB^XBH;^v!(g)I6i^8su=&5=Lw#uRrQKNQz7tHuA$4bk5>)GTEhH&Q6YnT-FhBWV zliW&-Z9ip|C;2chwo@uS&$!gDQD*!nW8vuO;BF&etYxO>w*16#V(i>`CyF}olZ_51 z#e55?{`s!hTO{%!Dd&Y-4^rd=nuHpkV&=N&WE55xZG7sGCR?X^STmmuR}rVUqR_M4 zPbTd`n|Z5AKR$KJ?>%qm=T&H#W0W=TQ+`wjCoRCkR3a%`j~aiAqBz49U#%1NeLZg1 zKZ{c}rp8Z)wo*GRA99;3-r$&P(UG*r>hd98$EO6(`jQY`-8X!ebV4|bBfr!@>RB+|!#G2waXR$2I@%&#^ zo8Pz1%C?GOQVv+#r^*Ce{HHH1X*u29w;IT-SuTDue1~eonvB|I{u~#1G@58+9XlA@ zq{JKSOfbU7l4WT15gkoe;TAp1q3V1J>rE}gS$I|v6x0I8CES4aQ4u_*D49nSfDng@z-YhZFs(=8v2wx zMdzPP0@o4%d_HU${t)8%rao4Y4lu3>{829268duK0g9 z|4>~Y5WX#W-rxLWxUSgla@{t(*jm|=r=X^i|8n=(xJnc*ozYunlpQ!M;?&-x< zI+bbJwZUKXf7eYtBF7+O*aZ(9$dXfZhlH-}3y$Lux>7hwSGV*U)MMy#{?8oRTsUi^e(&VIvkoq_jze0eeLb`p~^dCt=X4 z%5znmS^A5mQi(8lUOqstCF%xBi2HE<@DN}(sN@|E4c>jiy>7gTITnC>&-$0!lvh%d*{Qijh8~%JW4{B zP`h@0mJ14D6V+fD;|%0gRFkl&=eTs(QHs>ME7y7`u8|cp<92c6LAEOQ@X5j_OnY|i z(rqrS(opCWsv2MKS|Z!bDi}?>?Pi%GKGsN>py!(vSLVRsrJ3KUiP%xUjO>&>g4X1b zck_F^^{4Gmhj@~BQ7cpCGB)*DIfOpP2qqB+|NSSJT(sxAmdrfESPML3dm`Dn*Q`qX zdN8|o-96?cg5g8wPi2*7{%~nizn0@wel(Iy{j~>68!h1#sfy%cw|SENDOo1jpBo=q zk-v~!&7sOJD@iz|Wc>Er*zQn2w8MJ^Hu~X2dLs*u9C zNG7dleC=W5WWxbwpRWA35-an`6>+55vrqL;V9Ip3hvUE$WcRLJvr~ntgoO^vWlMRy zy;yg7uygHDLmN>_-p^SfLNM=32StV1@PUzcAr0jHSY$EoxSsT*#upzPVQW)g6B!U_ zt#g}N>{}5el$gFQ=MsTMys*3rXYAl&W0G#Y>_>g}Pl4q?RspM*ZuP z!K`C4sgs_E{pgK`bF{fJf!SXhv(Ih+I$5q3TetT#ZR(olI3{HqC%3Cfv7>&ztO5%! z>&)(7KY^LawwTW5$+p`5_a{RJPJS%SEUs&B@co9b@sb3q9zyJ;2@~YRul*xA^4PR>4?;f`s{4zaKt5Cc$zJ}7xuorXrG>@hp zDGgpY{jD%1^MWT!Xk&|`wI41>*mg}+bJ8@2D$cy`K~ z+)Sp$vDD+U-@Jmc}!wq3VaRUB%|5;>*yxE;FxEhjrS?y|8@Q>9TriYJn zo5dPSDoL|zQ=|0?>a;`AUmMZ+ux^a6kUh(wRx+@06xmHeE0?KVb$cfT3AuxlSs3!^ z*up3r-cRL5Urvm((e?_AD9q^EuUhtXa07J}JJx*pYJsWqY@DxOI79Kbqx43v!gy;o zga{ED{#?@gAQA-rBq4lHJS%^zqUVd%D;rTBd(7%lVN#mS!`>U|8^$KXwE1H> z6FF$Rf_O7$Gs06&OC8(SmlQ1F?w~MID3~yCXEd&{CAY#t+s7#(n}=3FUdp9-YkMyS zX|f1}3KnQ_XAIW4B~+oVlBU#UVV5gYde%KdhCmpB1C%snWQ&%i=PICnEPc<*Ii?}h) zwhm=3ySba*C{6kIdwHSMT{x9BY(YpB6=K#G-4Gr%R^G&@EHT>W#@ox^5fq6GJf6+)kc{?xN z@O4mYSj+R9r4WIsVfe_`Ms@`X=0}^q7Ol44o@z)*%;IZ3%UKmaQ+cy9=(H?iA6mL@N86_9KfM)t( zngqPnbzWqfu;^@{LPLp8F<-qibZ)HJmo-CHnWM%8-~Qn%8@YF}UvM`*0-}=#OTuGwi zrCN*?r3RMlw%^z3@6Z{IoK&4ZK*R8!L5L72ax7oC_T2VAF5xaV_W4jq7$Fjw6Z8I?Ys}TV|~}A)M50agELohNS|%%2ra3x}y_G%|G%XJJn_ zoTgG7UXMD4sVTzxINfs?dseIS6^Sh|q02g7I0H*h37Ll8y1p(*u=f$FZ5JjiH{2K^ zRpB0fny|Uv*}3(zOL#J8ZBATPyQuGpJF9TCOT-}>KDh24k14IyX>oZU?G@wnr>sGI z@^0gDTl*BlkbY*Sr!y`1M_q4}4V_Ub|A7=N=1aIo6|XVAG*IJe0o8o>gRRd#$8B|6 ztu&1OdQot|>!Q4pE6C=yUnPNug{C$kw7umg+t-Cn7UC zvs@vNdrY&4_ArLw*IWy$gRU^9(WU26C*7D#;d+7ad4=tbo83&N(o*M!YkJ1v+eVHF zVdcC|*MP%Y7qe5)o|F%)9jTq$dUvE6CC!(dE7~p(CY$3jv^$*ShF3|m=Ux{OWvZ=B2G&PL~p0Zh&haV)HX>Zmtwni_oy?TGh!E4v|>V6e84!dSzt5?4IHwvYJghvPZxAvzTsm&r96tUfu_M`=kj z$+~YgS*fYXxCJwLa8;fXx`e#xiC1O$FPmc$TV2mA-aR+=Hc_2(z=3P=^@rT~^X(^T z;T4WFP&-pe&6Qi-d(f93rbTzj0|8u@46^|8Ji(zaR2`Uo30~V literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_android.xml b/app/src/main/res/drawable/ic_android.xml new file mode 100644 index 00000000..0f0241ee --- /dev/null +++ b/app/src/main/res/drawable/ic_android.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_battery.xml b/app/src/main/res/drawable/ic_battery.xml new file mode 100644 index 00000000..4aef0305 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_cpu_temp.xml b/app/src/main/res/drawable/ic_cpu_temp.xml new file mode 100644 index 00000000..1998d616 --- /dev/null +++ b/app/src/main/res/drawable/ic_cpu_temp.xml @@ -0,0 +1,62 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_hardware.xml b/app/src/main/res/drawable/ic_hardware.xml new file mode 100644 index 00000000..1f81b00c --- /dev/null +++ b/app/src/main/res/drawable/ic_hardware.xml @@ -0,0 +1,62 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_process.xml b/app/src/main/res/drawable/ic_process.xml new file mode 100644 index 00000000..a0208d72 --- /dev/null +++ b/app/src/main/res/drawable/ic_process.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 00000000..cc93cbdd --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml new file mode 100644 index 00000000..45d01e3f --- /dev/null +++ b/app/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_temperature.xml b/app/src/main/res/drawable/ic_temperature.xml new file mode 100644 index 00000000..fd2981e1 --- /dev/null +++ b/app/src/main/res/drawable/ic_temperature.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thrash.xml b/app/src/main/res/drawable/ic_thrash.xml new file mode 100644 index 00000000..cf368e1e --- /dev/null +++ b/app/src/main/res/drawable/ic_thrash.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/layout/activity_host_layout.xml b/app/src/main/res/layout/activity_host_layout.xml new file mode 100644 index 00000000..5042c9b8 --- /dev/null +++ b/app/src/main/res/layout/activity_host_layout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_native_libs.xml b/app/src/main/res/layout/dialog_native_libs.xml new file mode 100644 index 00000000..7be32b99 --- /dev/null +++ b/app/src/main/res/layout/dialog_native_libs.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_applications.xml b/app/src/main/res/layout/fragment_applications.xml new file mode 100644 index 00000000..83e55045 --- /dev/null +++ b/app/src/main/res/layout/fragment_applications.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_info.xml b/app/src/main/res/layout/fragment_info.xml new file mode 100644 index 00000000..2514b5e0 --- /dev/null +++ b/app/src/main/res/layout/fragment_info.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_processes.xml b/app/src/main/res/layout/fragment_processes.xml new file mode 100644 index 00000000..9ea3f1b5 --- /dev/null +++ b/app/src/main/res/layout/fragment_processes.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rv.xml b/app/src/main/res/layout/fragment_rv.xml new file mode 100644 index 00000000..57339bd0 --- /dev/null +++ b/app/src/main/res/layout/fragment_rv.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_temperature.xml b/app/src/main/res/layout/fragment_temperature.xml new file mode 100644 index 00000000..5a207cb6 --- /dev/null +++ b/app/src/main/res/layout/fragment_temperature.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/header_nav_drawer.xml b/app/src/main/res/layout/header_nav_drawer.xml new file mode 100644 index 00000000..b5d04257 --- /dev/null +++ b/app/src/main/res/layout/header_nav_drawer.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_application.xml b/app/src/main/res/layout/item_application.xml new file mode 100644 index 00000000..4df7b42c --- /dev/null +++ b/app/src/main/res/layout/item_application.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_native_libs.xml b/app/src/main/res/layout/item_native_libs.xml new file mode 100644 index 00000000..7c26427d --- /dev/null +++ b/app/src/main/res/layout/item_native_libs.xml @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_preference_category.xml b/app/src/main/res/layout/item_preference_category.xml new file mode 100644 index 00000000..8945cfba --- /dev/null +++ b/app/src/main/res/layout/item_preference_category.xml @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_process.xml b/app/src/main/res/layout/item_process.xml new file mode 100644 index 00000000..be8856bc --- /dev/null +++ b/app/src/main/res/layout/item_process.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_storage.xml b/app/src/main/res/layout/item_storage.xml new file mode 100644 index 00000000..16387da3 --- /dev/null +++ b/app/src/main/res/layout/item_storage.xml @@ -0,0 +1,51 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_swipe_app.xml b/app/src/main/res/layout/item_swipe_app.xml new file mode 100644 index 00000000..cdaca088 --- /dev/null +++ b/app/src/main/res/layout/item_swipe_app.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_swipe_app_right.xml b/app/src/main/res/layout/item_swipe_app_right.xml new file mode 100644 index 00000000..4b965503 --- /dev/null +++ b/app/src/main/res/layout/item_swipe_app_right.xml @@ -0,0 +1,41 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_temperature.xml b/app/src/main/res/layout/item_temperature.xml new file mode 100644 index 00000000..4648cc80 --- /dev/null +++ b/app/src/main/res/layout/item_temperature.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_value.xml b/app/src/main/res/layout/item_value.xml new file mode 100644 index 00000000..15f9a9d2 --- /dev/null +++ b/app/src/main/res/layout/item_value.xml @@ -0,0 +1,44 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_value_vertical.xml b/app/src/main/res/layout/item_value_vertical.xml new file mode 100644 index 00000000..34cbc722 --- /dev/null +++ b/app/src/main/res/layout/item_value_vertical.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_icon_round_corner_progress_bar.xml b/app/src/main/res/layout/layout_icon_round_corner_progress_bar.xml new file mode 100644 index 00000000..4816dad6 --- /dev/null +++ b/app/src/main/res/layout/layout_icon_round_corner_progress_bar.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget.xml b/app/src/main/res/layout/widget.xml new file mode 100644 index 00000000..6f0f1b1b --- /dev/null +++ b/app/src/main/res/layout/widget.xml @@ -0,0 +1,44 @@ + + + + + + + + +