├── settings.gradle
├── app
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-hdpi
│ │ │ │ ├── ic_zoom.png
│ │ │ │ └── ic_screenshot.png
│ │ │ ├── drawable-mdpi
│ │ │ │ ├── ic_zoom.png
│ │ │ │ └── ic_screenshot.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── ic_zoom.png
│ │ │ │ └── ic_screenshot.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── ic_zoom.png
│ │ │ │ └── ic_screenshot.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── ic_zoom.png
│ │ │ │ └── ic_screenshot.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── zoomx
│ │ │ │ └── example
│ │ │ │ ├── model
│ │ │ │ └── User.java
│ │ │ │ ├── BaseApplication.java
│ │ │ │ ├── retrofit
│ │ │ │ ├── ApiService.java
│ │ │ │ └── NetworkManager.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── zoomx
│ │ │ └── example
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── zoomx
│ │ └── example
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── zoomx
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ ├── ic_screenshot.png
│ │ │ │ ├── ic_view_list.png
│ │ │ │ ├── ic_action_share.png
│ │ │ │ ├── ic_delete_forever.png
│ │ │ │ ├── ic_feature_collapse.png
│ │ │ │ └── ic_feature_settings.png
│ │ │ ├── drawable-mdpi
│ │ │ │ ├── ic_screenshot.png
│ │ │ │ ├── ic_view_list.png
│ │ │ │ ├── ic_action_share.png
│ │ │ │ ├── ic_delete_forever.png
│ │ │ │ ├── ic_feature_collapse.png
│ │ │ │ ├── ic_feature_settings.png
│ │ │ │ ├── ic_file_download_black_24dp.xml
│ │ │ │ └── ic_screenshot_24dp.xml
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── ic_screenshot.png
│ │ │ │ ├── ic_view_list.png
│ │ │ │ ├── ic_action_share.png
│ │ │ │ ├── ic_delete_forever.png
│ │ │ │ ├── ic_feature_collapse.png
│ │ │ │ └── ic_feature_settings.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── ic_view_list.png
│ │ │ │ ├── ic_screenshot.png
│ │ │ │ ├── ic_action_share.png
│ │ │ │ ├── ic_delete_forever.png
│ │ │ │ ├── ic_feature_collapse.png
│ │ │ │ └── ic_feature_settings.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── ic_screenshot.png
│ │ │ │ ├── ic_view_list.png
│ │ │ │ ├── ic_delete_forever.png
│ │ │ │ ├── ic_feature_collapse.png
│ │ │ │ └── ic_feature_settings.png
│ │ │ ├── xml
│ │ │ │ ├── searchable.xml
│ │ │ │ └── file_provider_paths.xml
│ │ │ ├── drawable
│ │ │ │ ├── menu_main_btn.xml
│ │ │ │ ├── row_selector.xml
│ │ │ │ ├── ic_swap_vert_black_24dp.xml
│ │ │ │ ├── ic_delete_black_24dp.xml
│ │ │ │ ├── ic_request_details_arrow.xml
│ │ │ │ ├── ic_feature_info.xml
│ │ │ │ └── ic_settings_black_24dp.xml
│ │ │ ├── menu
│ │ │ │ ├── menu_requests_details.xml
│ │ │ │ └── menu_request_list.xml
│ │ │ ├── layout
│ │ │ │ ├── view_menu_close.xml
│ │ │ │ ├── request_list_fragment.xml
│ │ │ │ ├── view_info_section.xml
│ │ │ │ ├── activity_request_details.xml
│ │ │ │ ├── activity_info.xml
│ │ │ │ ├── request_activity_layout.xml
│ │ │ │ ├── zoomx_ui_options_checkboxes.xml
│ │ │ │ ├── info_text_view.xml
│ │ │ │ ├── screenshot_viewer.xml
│ │ │ │ ├── info_fragment.xml
│ │ │ │ ├── request_details_item_row.xml
│ │ │ │ ├── activity_body.xml
│ │ │ │ ├── request_item_row.xml
│ │ │ │ ├── activity_settings.xml
│ │ │ │ ├── view_main_actions_menu.xml
│ │ │ │ └── request_details_fragment.xml
│ │ │ └── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── zoomx
│ │ │ │ └── zoomx
│ │ │ │ ├── util
│ │ │ │ ├── ZoomxFileProvider.java
│ │ │ │ ├── constants
│ │ │ │ │ └── PrefConstants.kt
│ │ │ │ ├── ErrorConstants.java
│ │ │ │ ├── ServiceUtils.java
│ │ │ │ ├── ColorUtils.java
│ │ │ │ ├── FileUtils.kt
│ │ │ │ ├── FormatUtil.java
│ │ │ │ ├── SharedPreferenceManager.java
│ │ │ │ ├── ShakeEventManager.java
│ │ │ │ └── PhoneUtils.java
│ │ │ │ ├── ui
│ │ │ │ ├── ZoomxUIOption.java
│ │ │ │ ├── notification
│ │ │ │ │ ├── ClearLogsIntentService.kt
│ │ │ │ │ ├── ScreenshotViewerActivity.kt
│ │ │ │ │ ├── ZoomxNotification.kt
│ │ │ │ │ └── ScreenShotActivity.kt
│ │ │ │ ├── requestdetails
│ │ │ │ │ ├── RequestDetailsViewModel.java
│ │ │ │ │ ├── RequestDetailsActivity.java
│ │ │ │ │ ├── RequestDetailsAdapter.java
│ │ │ │ │ ├── RequestDetailsViewHolder.java
│ │ │ │ │ ├── JsonViewActivity.java
│ │ │ │ │ └── RequestDetailsFragment.java
│ │ │ │ ├── menu
│ │ │ │ │ ├── MenuCloseView.java
│ │ │ │ │ ├── ZoomxMenuService.java
│ │ │ │ │ └── MainActionMenu.java
│ │ │ │ ├── info
│ │ │ │ │ ├── InfoActivity.java
│ │ │ │ │ ├── InfoSectionView.java
│ │ │ │ │ └── InfoFragment.java
│ │ │ │ ├── requestlist
│ │ │ │ │ ├── RequestActivity.java
│ │ │ │ │ ├── RequestListViewModel.java
│ │ │ │ │ ├── RequestViewHolder.java
│ │ │ │ │ ├── RequestAdapter.java
│ │ │ │ │ └── RequestListFragment.java
│ │ │ │ └── settings
│ │ │ │ │ ├── SettingsManager.kt
│ │ │ │ │ └── SettingActivity.kt
│ │ │ │ ├── db
│ │ │ │ ├── converters
│ │ │ │ │ ├── DateConverter.java
│ │ │ │ │ └── HeaderConverter.java
│ │ │ │ ├── RequestDao.java
│ │ │ │ └── AppDatabase.java
│ │ │ │ ├── model
│ │ │ │ ├── HeaderViewModel.java
│ │ │ │ ├── InfoModel.java
│ │ │ │ └── RequestEntity.java
│ │ │ │ ├── networklogger
│ │ │ │ ├── ZoomXLogManager.kt
│ │ │ │ └── ZoomXLoggerInterceptor.java
│ │ │ │ └── config
│ │ │ │ ├── Config.kt
│ │ │ │ └── ZoomX.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── zoomx
│ │ │ └── zoomx
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── zoomx
│ │ └── zoomx
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── gradle.properties
├── .gitignore
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':zoomx'
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Zoomx
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-hdpi/ic_zoom.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-mdpi/ic_zoom.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-xhdpi/ic_zoom.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-xxhdpi/ic_zoom.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-xxxhdpi/ic_zoom.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-hdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-mdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-xhdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-xxhdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/drawable-xxxhdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-hdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-hdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-hdpi/ic_view_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-hdpi/ic_view_list.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-mdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_view_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-mdpi/ic_view_list.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xhdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xhdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xhdpi/ic_view_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xhdpi/ic_view_list.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxhdpi/ic_view_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxhdpi/ic_view_list.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-hdpi/ic_action_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-hdpi/ic_action_share.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_action_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-mdpi/ic_action_share.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xhdpi/ic_action_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xhdpi/ic_action_share.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxhdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxhdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxxhdpi/ic_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxxhdpi/ic_screenshot.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxxhdpi/ic_view_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxxhdpi/ic_view_list.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-hdpi/ic_delete_forever.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-hdpi/ic_delete_forever.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-hdpi/ic_feature_collapse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-hdpi/ic_feature_collapse.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-hdpi/ic_feature_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-hdpi/ic_feature_settings.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_delete_forever.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-mdpi/ic_delete_forever.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_feature_collapse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-mdpi/ic_feature_collapse.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_feature_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-mdpi/ic_feature_settings.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xhdpi/ic_delete_forever.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xhdpi/ic_delete_forever.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxhdpi/ic_action_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxhdpi/ic_action_share.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxhdpi/ic_delete_forever.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxhdpi/ic_delete_forever.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xhdpi/ic_feature_collapse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xhdpi/ic_feature_collapse.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xhdpi/ic_feature_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xhdpi/ic_feature_settings.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxhdpi/ic_feature_collapse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxhdpi/ic_feature_collapse.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxhdpi/ic_feature_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxhdpi/ic_feature_settings.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxxhdpi/ic_delete_forever.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxxhdpi/ic_delete_forever.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxxhdpi/ic_feature_collapse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxxhdpi/ic_feature_collapse.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-xxxhdpi/ic_feature_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/district0/ZoomX/HEAD/zoomx/src/main/res/drawable-xxxhdpi/ic_feature_settings.png
--------------------------------------------------------------------------------
/zoomx/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 03 19:15:15 EET 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
7 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/ZoomxFileProvider.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.support.v4.content.FileProvider;
4 |
5 | /**
6 | * Created by Ahmed Fathallah on 3/5/2018.
7 | */
8 |
9 | public class ZoomxFileProvider extends FileProvider {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/menu_main_btn.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/constants/PrefConstants.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util.constants
2 |
3 | /**
4 | * Created by Ahmed Fathallah on 1/29/2018.
5 | */
6 |
7 | class PrefConstants {
8 | companion object {
9 |
10 | const val NETWORK_TRACKER_KEY = "networkTrackerKey"
11 | const val ZOOMX_UI_OPTION_KEY = "zoomx_ui_option"
12 |
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/row_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/ic_swap_vert_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/ZoomxUIOption.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | /**
6 | * Created by Mohamed Ibrahim on 12/8/18.
7 | */
8 | @IntDef({
9 | ZoomxUIOption.DRAW_OVER_APPS,
10 | ZoomxUIOption.NOTIFICATION
11 | })
12 | public @interface ZoomxUIOption {
13 | int NOTIFICATION = 0;
14 | int DRAW_OVER_APPS = 1;
15 | }
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/ic_delete_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/ic_request_details_arrow.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_file_download_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/menu/menu_requests_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/xml/file_provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
10 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/view_menu_close.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable-mdpi/ic_screenshot_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/notification/ClearLogsIntentService.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.notification
2 |
3 | import android.app.IntentService
4 | import android.content.Intent
5 |
6 | /**
7 | *
8 | * Created by Mohamed Ibrahim on 12/9/18.
9 | */
10 | class ClearLogsIntentService(private val name: String = "ClearLogsIntentService") : IntentService(name) {
11 |
12 | override fun onHandleIntent(intent: Intent?) {
13 | ZoomxNotification(this).clear()
14 | }
15 | }
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/ic_feature_info.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zoomx/example/model/User.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example.model;
2 |
3 | /**
4 | * Created by Ahmed Fathallah on 11/20/2017.
5 | */
6 |
7 | public class User {
8 | private String login;
9 | private long id;
10 | private String url;
11 |
12 | public String getLogin() {
13 | return login;
14 | }
15 |
16 | public long getId() {
17 | return id;
18 | }
19 |
20 | public String getUrl() {
21 | return url;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/test/java/com/zoomx/example/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/zoomx/src/test/java/com/zoomx/zoomx/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/zoomx/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F5F5F5
4 | #4CAF50
5 | #9E9E9E
6 | #EEEEEE
7 | #3F51B5
8 | #303F9F
9 | #FF4081
10 | #FFFFFF
11 | #000000
12 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/ErrorConstants.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | /**
9 | * Created by Ibrahim AbdelGawad on 1/11/2018.
10 | */
11 |
12 | public class ErrorConstants {
13 |
14 | public static final int CONNECTION_ERROR = 0;
15 |
16 | @IntDef({CONNECTION_ERROR})
17 | @Retention(RetentionPolicy.SOURCE)
18 | public @interface Errors {};
19 | }
20 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestdetails/RequestDetailsViewModel.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestdetails;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.ViewModel;
5 |
6 | import com.zoomx.zoomx.config.ZoomX;
7 | import com.zoomx.zoomx.model.RequestEntity;
8 |
9 | /**
10 | * Created by Ibrahim AbdelGawad on 12/13/2017.
11 | */
12 |
13 | public class RequestDetailsViewModel extends ViewModel {
14 |
15 | public LiveData getRequestById(int id) {
16 | return ZoomX.getRequestDao().getRequestById(id);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zoomx/example/BaseApplication.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example;
2 |
3 | import android.support.multidex.MultiDexApplication;
4 |
5 | import com.zoomx.zoomx.config.Config;
6 | import com.zoomx.zoomx.config.ZoomX;
7 |
8 |
9 | /**
10 | * Created by Ahmed Fathallah on 12/11/2017.
11 | */
12 |
13 | public class BaseApplication extends MultiDexApplication {
14 |
15 | @Override
16 | public void onCreate() {
17 | super.onCreate();
18 | ZoomX.init(new Config.Builder(this)
19 | .showMenuOnAppStart(false)
20 | .build());
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/db/converters/DateConverter.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.db.converters;
2 |
3 | import android.arch.persistence.room.TypeConverter;
4 |
5 | import java.util.Date;
6 |
7 | /**
8 | * Created by Ahmed Fathallah on 12/6/2017.
9 | */
10 |
11 | public class DateConverter {
12 | @TypeConverter
13 | public static Date toDate(Long timestamp) {
14 | return timestamp == null ? null : new Date(timestamp);
15 | }
16 |
17 | @TypeConverter
18 | public static Long toTimestamp(Date date) {
19 | return date == null ? null : date.getTime();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/request_list_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/menu/MenuCloseView.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.menu;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.view.View;
6 | import android.widget.FrameLayout;
7 |
8 | import com.zoomx.zoomx.R;
9 |
10 | /**
11 | * Created by Ahmed Fathallah on 1/28/2018.
12 | */
13 |
14 | public class MenuCloseView extends FrameLayout {
15 |
16 | public MenuCloseView(@NonNull Context context) {
17 | super(context);
18 | initUI();
19 | }
20 |
21 | private void initUI() {
22 | View view = inflate(getContext(), R.layout.view_menu_close, this);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zoomx/example/retrofit/ApiService.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example.retrofit;
2 |
3 | import com.zoomx.example.model.User;
4 |
5 | import java.util.List;
6 |
7 | import retrofit2.http.GET;
8 | import retrofit2.http.Headers;
9 | import retrofit2.http.Query;
10 |
11 | /**
12 | * Created by Ahmed Fathallah on 11/20/2017.
13 | */
14 |
15 | public interface ApiService {
16 | @GET("/users")
17 | @Headers({
18 | "X-Foo: Bar",
19 | "X-Ping: Pong",
20 | })
21 | public io.reactivex.Observable> getUsers(
22 | @Query("per_page") int per_page,
23 | @Query("page") int page);
24 |
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/view_info_section.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/db/converters/HeaderConverter.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.db.converters;
2 |
3 | import android.arch.persistence.room.TypeConverter;
4 |
5 | import com.google.gson.Gson;
6 | import com.zoomx.zoomx.model.HeaderViewModel;
7 |
8 | /**
9 | * Created by Ahmed Fathallah on 12/6/2017.
10 | */
11 |
12 | public class HeaderConverter {
13 | @TypeConverter
14 | public static String toString(HeaderViewModel headersModel) {
15 | return new Gson().toJson(headersModel);
16 | }
17 |
18 | @TypeConverter
19 | public static HeaderViewModel toModel(String headers) {
20 | return new Gson().fromJson(headers, HeaderViewModel.class);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/ServiceUtils.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 |
6 | public class ServiceUtils {
7 | public static boolean isMyServiceRunning(Class> serviceClass, Context context) {
8 | ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
9 | for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
10 | if (serviceClass.getName().equals(service.service.getClassName())) {
11 | return true;
12 | }
13 | }
14 | return false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/menu/menu_request_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/ColorUtils.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.support.v4.content.ContextCompat;
6 |
7 | import com.zoomx.zoomx.R;
8 |
9 | /**
10 | * Created by Ibrahim AbdelGawad on 12/12/2017.
11 | */
12 |
13 | public class ColorUtils {
14 |
15 | public static int getCodeColor(int code, Context context) {
16 |
17 | int color;
18 |
19 | switch (code) {
20 | case 200:
21 | color = ContextCompat.getColor(context, R.color.green_500);
22 | break;
23 |
24 | default:
25 | color = Color.RED;
26 | break;
27 | }
28 |
29 | return color;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/model/HeaderViewModel.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | /**
8 | * Created by Ahmed Fathallah on 11/20/2017.
9 | */
10 | public class HeaderViewModel {
11 |
12 | private ArrayList headersMap;
13 |
14 | public HeaderViewModel() {
15 | this.headersMap = new ArrayList<>();
16 | }
17 |
18 | public HeaderViewModel(ArrayList headersMap) {
19 | this.headersMap = headersMap;
20 | }
21 |
22 | public void addHeader(String name, String value) {
23 | headersMap.add(name + ": " + value);
24 | }
25 |
26 | public ArrayList getHeadersMap() {
27 | return headersMap;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/activity_request_details.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/zoomx/example/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.assertEquals;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.zoomx.zoomx", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/zoomx/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class android.support.v7.widget.SearchView { *; }
24 |
--------------------------------------------------------------------------------
/zoomx/src/androidTest/java/com/zoomx/zoomx/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.assertEquals;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.zoomx.zoomx.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/info/InfoActivity.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.info;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.Toolbar;
6 |
7 | import com.zoomx.zoomx.R;
8 |
9 | public class InfoActivity extends AppCompatActivity {
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_info);
14 | initUI();
15 | }
16 |
17 | private void initUI() {
18 | Toolbar toolbar = findViewById(R.id.toolbar);
19 | setSupportActionBar(toolbar);
20 | InfoFragment infoFragment = new InfoFragment();
21 | getSupportFragmentManager()
22 | .beginTransaction()
23 | .add(R.id.container, infoFragment)
24 | .commit();
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/activity_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/request_activity_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/zoomx_ui_options_checkboxes.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestlist/RequestActivity.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestlist;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.Toolbar;
7 |
8 | import com.zoomx.zoomx.R;
9 |
10 | /**
11 | * Created by Ibrahim AbdelGawad on 12/3/2017.
12 | */
13 |
14 | public class RequestActivity extends AppCompatActivity {
15 |
16 | @Override
17 | protected void onCreate(@Nullable Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.request_activity_layout);
20 | Toolbar toolbar = findViewById(R.id.toolbar);
21 | setSupportActionBar(toolbar);
22 | getSupportFragmentManager()
23 | .beginTransaction()
24 | .add(R.id.container, new RequestListFragment())
25 | .commit();
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/info_text_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/db/RequestDao.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.db;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.persistence.room.Dao;
5 | import android.arch.persistence.room.Insert;
6 | import android.arch.persistence.room.OnConflictStrategy;
7 | import android.arch.persistence.room.Query;
8 |
9 | import com.zoomx.zoomx.model.RequestEntity;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * Created by Ahmed Fathallah on 12/6/2017.
15 | */
16 |
17 | @Dao
18 | public interface RequestDao {
19 |
20 | @Insert(onConflict = OnConflictStrategy.REPLACE)
21 | void insertRequest(RequestEntity requestEntity);
22 |
23 | @Query("SELECT * FROM requests ORDER BY startDate DESC")
24 | LiveData> loadRequests();
25 |
26 | @Query("DELETE FROM requests")
27 | int clearRequestsData();
28 |
29 | @Query("SELECT * FROM requests WHERE id = :id")
30 | LiveData getRequestById(int id);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
19 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util
2 |
3 | import java.io.File
4 | import java.io.FileInputStream
5 | import java.io.FileOutputStream
6 | import java.io.IOException
7 | import java.nio.channels.FileChannel
8 |
9 | @Throws(IOException::class)
10 | fun copyFile(sourceFile: File, destFile: File) {
11 | if (!destFile.parentFile.exists())
12 | destFile.parentFile.mkdirs()
13 |
14 | if (!destFile.exists()) {
15 | destFile.createNewFile()
16 | }
17 |
18 | var source: FileChannel? = null
19 | var destination: FileChannel? = null
20 |
21 | try {
22 | source = FileInputStream(sourceFile).channel
23 | destination = FileOutputStream(destFile).channel
24 | destination!!.transferFrom(source, 0, source!!.size())
25 | } finally {
26 | if (source != null) {
27 | source!!.close()
28 | }
29 | if (destination != null) {
30 | destination!!.close()
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/dictionaries
41 | .idea/libraries
42 |
43 | # Keystore files
44 | *.jks
45 |
46 | # External native build folder generated in Android Studio 2.2 and later
47 | .externalNativeBuild
48 |
49 | # Google Services (e.g. APIs or Firebase)
50 | google-services.json
51 |
52 | # Freeline
53 | freeline.py
54 | freeline/
55 | freeline_project_description.json
56 | /.idea/
57 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/screenshot_viewer.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
14 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ahmed Fathallah
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/info_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
22 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestdetails/RequestDetailsActivity.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestdetails;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.Toolbar;
6 |
7 | import com.zoomx.zoomx.R;
8 | import com.zoomx.zoomx.ui.requestlist.RequestListFragment;
9 |
10 | public class RequestDetailsActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_request_details);
16 |
17 | Toolbar toolbar = findViewById(R.id.toolbar);
18 | setSupportActionBar(toolbar);
19 |
20 | if (getIntent() != null && getIntent().getExtras() != null) {
21 | int requestId = getIntent().getExtras().getInt(RequestListFragment.REQUEST_ID, 1);
22 | getSupportFragmentManager()
23 | .beginTransaction()
24 | .add(R.id.container, RequestDetailsFragment.newInstance(requestId))
25 | .commit();
26 | }
27 |
28 | }
29 |
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/drawable/ic_settings_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestlist/RequestListViewModel.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestlist;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.ViewModel;
5 | import com.zoomx.zoomx.config.ZoomX;
6 | import com.zoomx.zoomx.model.RequestEntity;
7 | import io.reactivex.Completable;
8 | import io.reactivex.android.schedulers.AndroidSchedulers;
9 | import io.reactivex.schedulers.Schedulers;
10 |
11 | import java.util.List;
12 | import java.util.concurrent.Callable;
13 |
14 | /**
15 | * Created by Ahmed Fathallah on 12/11/2017.
16 | */
17 |
18 | public class RequestListViewModel extends ViewModel {
19 |
20 | private LiveData> mRequests;
21 |
22 | public RequestListViewModel() {
23 | mRequests = ZoomX.getRequestDao().loadRequests();
24 | }
25 |
26 | public LiveData> getRequests() {
27 | return mRequests;
28 | }
29 |
30 | public Completable clearRequestFromDB() {
31 | return Completable
32 | .fromCallable(ZoomX.getRequestDao()::clearRequestsData)
33 | .observeOn(AndroidSchedulers.mainThread())
34 | .subscribeOn(Schedulers.io());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/settings/SettingsManager.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.settings
2 |
3 | import android.content.Context
4 | import com.zoomx.zoomx.ui.ZoomxUIOption
5 | import com.zoomx.zoomx.util.SharedPreferenceManager
6 | import com.zoomx.zoomx.util.constants.PrefConstants
7 |
8 | /**
9 | * Created by Ibrahim AbdelGawad on 12/19/2017.
10 | */
11 |
12 | class SettingsManager(private val sharedPreferenceManager: SharedPreferenceManager) {
13 |
14 |
15 | val isNetworkTrackingEnabled: Boolean?
16 | get() = sharedPreferenceManager.getBoolean(PrefConstants.NETWORK_TRACKER_KEY, true)
17 |
18 | var zoomxUIOption: Int
19 | @ZoomxUIOption
20 | get() = sharedPreferenceManager.getInt(PrefConstants.ZOOMX_UI_OPTION_KEY, ZoomxUIOption.DRAW_OVER_APPS)
21 | set(@ZoomxUIOption value) {
22 | sharedPreferenceManager.saveInt(PrefConstants.ZOOMX_UI_OPTION_KEY, value)
23 | }
24 |
25 | fun setNetworkTrackingStatus(trackingStatus: Boolean) {
26 | sharedPreferenceManager.saveBoolean(PrefConstants.NETWORK_TRACKER_KEY, trackingStatus)
27 | }
28 |
29 | companion object {
30 | @JvmStatic
31 | operator fun get(context: Context): SettingsManager {
32 | return SettingsManager(SharedPreferenceManager(context))
33 | }
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/db/AppDatabase.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.db;
2 |
3 | import android.arch.persistence.room.Database;
4 | import android.arch.persistence.room.Room;
5 | import android.arch.persistence.room.RoomDatabase;
6 | import android.arch.persistence.room.TypeConverters;
7 | import android.content.Context;
8 |
9 | import com.zoomx.zoomx.db.converters.DateConverter;
10 | import com.zoomx.zoomx.model.RequestEntity;
11 |
12 | /**
13 | * Created by Ahmed Fathallah on 12/6/2017.
14 | */
15 | @Database(entities = {RequestEntity.class}, version = 1, exportSchema = false)
16 | @TypeConverters(DateConverter.class)
17 | public abstract class AppDatabase extends RoomDatabase {
18 |
19 | private static final String DB_NAME = "ZoomXDB";
20 | private static AppDatabase database;
21 |
22 | AppDatabase() {
23 | }
24 |
25 | public static AppDatabase get(Context context) {
26 | synchronized (AppDatabase.class) {
27 | if (database == null) {
28 | database = buildDataBase(context);
29 | }
30 | }
31 | return database;
32 | }
33 |
34 | private static AppDatabase buildDataBase(Context context) {
35 | return Room.databaseBuilder(context, AppDatabase.class, DB_NAME)
36 | .build();
37 | }
38 |
39 | public abstract RequestDao requestDao();
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/request_details_item_row.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
19 |
20 |
27 |
28 |
29 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/networklogger/ZoomXLogManager.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.networklogger
2 |
3 | import com.zoomx.zoomx.config.ZoomX
4 | import com.zoomx.zoomx.model.RequestEntity
5 | import com.zoomx.zoomx.ui.ZoomxUIOption
6 | import io.reactivex.Completable
7 | import io.reactivex.android.schedulers.AndroidSchedulers
8 | import io.reactivex.schedulers.Schedulers
9 | import timber.log.Timber
10 |
11 | /**
12 | * Created by Ahmed Fathallah on 1/11/2018.
13 | */
14 |
15 | class ZoomXLogManager {
16 |
17 | companion object {
18 |
19 | @JvmStatic
20 | fun log(builder: RequestEntity.Builder) {
21 | insertRequest(builder).subscribe()
22 | }
23 |
24 | @JvmStatic
25 | private fun insertRequest(builder: RequestEntity.Builder): Completable {
26 | return Completable
27 | .fromAction { ZoomX.getRequestDao().insertRequest(builder.create()) }
28 | .doOnError { Timber.e(it) }
29 | .observeOn(AndroidSchedulers.mainThread())
30 | .andThen {
31 | if (ZoomX.getSettingsManager()?.zoomxUIOption == ZoomxUIOption.NOTIFICATION) {
32 | ZoomX.getNotification()?.show(builder.create())
33 | }
34 | }
35 | .subscribeOn(Schedulers.io())
36 | }
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/FormatUtil.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import java.io.IOException;
4 | import java.text.DateFormat;
5 | import java.text.SimpleDateFormat;
6 | import java.util.Date;
7 | import java.util.Locale;
8 |
9 | import okhttp3.RequestBody;
10 | import okio.Buffer;
11 |
12 | /**
13 | * Created by Ibrahim AbdelGawad on 12/12/2017.
14 | */
15 |
16 | public class FormatUtil {
17 |
18 | final public static String DATE_FORMAT = "yyyy-MM-dd hh:mm:ss a";
19 |
20 | public static String formatDate(Date date, String format) {
21 | String formattedString = "";
22 | if (date != null) {
23 | try {
24 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
25 | formattedString = simpleDateFormat.format(date);
26 | } catch (Exception e) {
27 | return "";
28 | }
29 | }
30 | return formattedString;
31 | }
32 |
33 | public static String bodyToString(final RequestBody request){
34 | try {
35 | final Buffer buffer = new Buffer();
36 | if(request != null)
37 | request.writeTo(buffer);
38 | else
39 | return "";
40 | return buffer.readUtf8();
41 | }
42 | catch (final IOException e) {
43 | return "did not work";
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/activity_body.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
20 |
21 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zoomx/example/retrofit/NetworkManager.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example.retrofit;
2 |
3 | import android.content.Context;
4 | import com.zoomx.zoomx.networklogger.ZoomXLoggerInterceptor;
5 | import okhttp3.OkHttpClient;
6 | import retrofit2.Retrofit;
7 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
8 | import retrofit2.converter.gson.GsonConverterFactory;
9 |
10 | /**
11 | * Created by Ahmed Fathallah on 11/20/2017.
12 | */
13 |
14 | public class NetworkManager {
15 |
16 | private Retrofit retrofit;
17 | private ApiService apiService;
18 | private Context context;
19 |
20 | public NetworkManager(Context context) {
21 | this.context = context;
22 | initRetrofit();
23 | }
24 |
25 | private void initRetrofit() {
26 | OkHttpClient.Builder httpClient = new OkHttpClient
27 | .Builder()
28 | .addInterceptor(new ZoomXLoggerInterceptor(this.context));
29 |
30 | retrofit = new Retrofit.Builder()
31 | .baseUrl("https://api.github.com/")
32 | .addConverterFactory(GsonConverterFactory.create())
33 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
34 | .client(httpClient.build())
35 | .build();
36 | }
37 |
38 | public ApiService service() {
39 | if (apiService == null) {
40 | apiService = retrofit.create(ApiService.class);
41 | }
42 | return apiService;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | multiDexEnabled true
7 | applicationId "com.zoomx.example"
8 | minSdkVersion 17
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 |
21 | compileOptions {
22 | sourceCompatibility JavaVersion.VERSION_1_8
23 | targetCompatibility JavaVersion.VERSION_1_8
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(include: ['*.jar'], dir: 'libs')
29 | implementation 'com.android.support:appcompat-v7:26.1.0'
30 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
31 |
32 | testImplementation 'junit:junit:4.12'
33 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
34 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
35 |
36 | implementation 'com.squareup.retrofit2:retrofit:2.3.0'
37 | implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
38 | implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
39 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
40 |
41 | implementation project(':zoomx')
42 | implementation 'android.arch.lifecycle:extensions:1.1.1'
43 | implementation 'com.android.support:multidex:1.0.3'
44 | }
45 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestdetails/RequestDetailsAdapter.java:
--------------------------------------------------------------------------------
1 |
2 | package com.zoomx.zoomx.ui.requestdetails;
3 |
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import com.zoomx.zoomx.R;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * Created by Ibrahim AbdelGawad on 12/13/2017.
16 | */
17 | public class RequestDetailsAdapter extends RecyclerView.Adapter {
18 |
19 | private ArrayList requestHeaderList;
20 |
21 | public RequestDetailsAdapter(ArrayList requestHeaderList, ArrayList responseHeadersMap) {
22 | this.requestHeaderList = new ArrayList<>();
23 | this.requestHeaderList.add("Request:Request");
24 | this.requestHeaderList.addAll(requestHeaderList);
25 | this.requestHeaderList.add("Response:Response");
26 | this.requestHeaderList.addAll(responseHeadersMap);
27 | }
28 |
29 | @Override
30 | public RequestDetailsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
31 | View view = LayoutInflater.from(parent.getContext())
32 | .inflate(R.layout.request_details_item_row, parent, false);
33 |
34 | return new RequestDetailsViewHolder(view);
35 | }
36 |
37 | @Override
38 | public void onBindViewHolder(RequestDetailsViewHolder holder, int position) {
39 | try {
40 | holder.bind(requestHeaderList.get(position), position);
41 | }catch (Exception e){
42 | e.printStackTrace();
43 | }
44 | }
45 |
46 | @Override
47 | public int getItemCount() {
48 | return requestHeaderList.size();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/config/Config.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.config
2 |
3 | import android.content.Context
4 | import com.zoomx.zoomx.ui.ZoomxUIOption
5 |
6 | /**
7 | * Created by Ahmed Fathallah on 12/11/2017.
8 | */
9 |
10 | class Config internal constructor(builder: Builder) {
11 |
12 | val context: Context
13 | private val showMenuOnAppStart: Boolean
14 | private val showOnShakeEvent: Boolean
15 | val zoomxUIOption: Int
16 |
17 | init {
18 | this.context = builder.context
19 | this.showMenuOnAppStart = builder.showMenuOnAppStart
20 | this.showOnShakeEvent = builder.showOnShakeEvent
21 | this.zoomxUIOption = builder.zoomxUiOption
22 | }
23 |
24 | fun canShowMenuOnAppStart(): Boolean {
25 | return showMenuOnAppStart
26 | }
27 |
28 | fun canShowOnShakeEvent(): Boolean {
29 | return showOnShakeEvent
30 | }
31 |
32 | class Builder(internal var context: Context) {
33 | internal var showMenuOnAppStart: Boolean = false
34 | internal var showOnShakeEvent: Boolean = false
35 | internal var zoomxUiOption: Int = ZoomxUIOption.DRAW_OVER_APPS
36 |
37 | fun showMenuOnAppStart(showMenuOnAppStart: Boolean): Builder {
38 | this.showMenuOnAppStart = showMenuOnAppStart
39 | return this
40 | }
41 |
42 | fun showOnShakeEvent(showOnShakeEvent: Boolean): Builder {
43 | this.showOnShakeEvent = showOnShakeEvent
44 | return this
45 | }
46 |
47 | fun setZoomxUIOptions(@ZoomxUIOption option: Int): Builder {
48 | this.zoomxUiOption = option
49 | return this
50 | }
51 |
52 | fun build(): Config {
53 | return Config(this)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/request_item_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
25 |
26 |
31 |
32 |
38 |
39 |
44 |
45 |
46 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/model/InfoModel.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.model;
2 |
3 | /**
4 | * Created by Ahmed Fathallah on 2/15/2018.
5 | */
6 |
7 | public class InfoModel {
8 | public static final int TEXT_VIEW_TYPE = 601;
9 | public static final int SWITCH_VIEW_TYPE = 602;
10 | public static final int BUTTON_VIEW_TYPE = 603;
11 |
12 | private String title;
13 | private int type;
14 | private String value;
15 | private boolean state;
16 | private Runnable buttonAction;
17 |
18 | public InfoModel(int type, String title, String value) {
19 | this.title = title;
20 | this.type = type;
21 | this.value = value;
22 | this.buttonAction = null;
23 | }
24 |
25 | public InfoModel(int type, String title, String value, Runnable buttonAction) {
26 | this.title = title;
27 | this.type = type;
28 | this.value = value;
29 | this.buttonAction = buttonAction;
30 | }
31 |
32 | public String getTitle() {
33 | return title;
34 | }
35 |
36 | public void setTitle(String title) {
37 | this.title = title;
38 | }
39 |
40 | public int getType() {
41 | return type;
42 | }
43 |
44 | public void setType(int type) {
45 | this.type = type;
46 | }
47 |
48 | public String getValue() {
49 | return value;
50 | }
51 |
52 | public void setValue(String value) {
53 | this.value = value;
54 | }
55 |
56 | public boolean isState() {
57 | return state;
58 | }
59 |
60 | public void setState(boolean state) {
61 | this.state = state;
62 | }
63 |
64 | public boolean isTextViewType() {
65 | return TEXT_VIEW_TYPE == type;
66 | }
67 |
68 | public boolean isSwitchType() {
69 | return SWITCH_VIEW_TYPE == type;
70 | }
71 |
72 | public boolean isButtonType() {
73 | return BUTTON_VIEW_TYPE == type;
74 | }
75 |
76 | public Runnable getButtonAction() {
77 | return buttonAction;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestlist/RequestViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestlist;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import com.zoomx.zoomx.R;
8 | import com.zoomx.zoomx.model.RequestEntity;
9 | import com.zoomx.zoomx.util.ColorUtils;
10 | import com.zoomx.zoomx.util.ErrorConstants;
11 | import com.zoomx.zoomx.util.FormatUtil;
12 |
13 | /**
14 | * Created by Ibrahim AbdelGawad on 12/13/2017.
15 | */
16 |
17 | public class RequestViewHolder extends RecyclerView.ViewHolder {
18 | TextView urlTextView, codeTextView, methodTypeTextView, startDateTextView;
19 |
20 | public RequestViewHolder(View view) {
21 | super(view);
22 | urlTextView = view.findViewById(R.id.url_tx);
23 | codeTextView = view.findViewById(R.id.details_code_txt);
24 | methodTypeTextView = view.findViewById(R.id.details_method_txt);
25 | startDateTextView = view.findViewById(R.id.date_txt);
26 | }
27 |
28 | public void bind(final RequestEntity requestEntity, final RequestAdapter.OnRequestItemClickListener onRequestItemClickListener) {
29 | if (requestEntity != null) {
30 | urlTextView.setText(requestEntity.getUrl());
31 | methodTypeTextView.setText(requestEntity.getMethod());
32 | codeTextView.setText(requestEntity.getCode() == ErrorConstants.CONNECTION_ERROR ? itemView.getContext().getString(R.string.request_error_code_text)
33 | : String.valueOf(requestEntity.getCode()));
34 | codeTextView.setTextColor(ColorUtils.getCodeColor(requestEntity.getCode(), itemView.getContext()));
35 | startDateTextView.setText(FormatUtil.formatDate(requestEntity.getStartDate(), FormatUtil.DATE_FORMAT));
36 | itemView.setOnClickListener(v -> {
37 | if (onRequestItemClickListener != null) {
38 | onRequestItemClickListener.onItemClick(requestEntity);
39 | }
40 | });
41 | }
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/zoomx/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'com.github.dcendents.android-maven'
5 | group='com.github.district0'
6 | android {
7 | compileSdkVersion 26
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 26
12 | versionCode 1
13 | versionName "1.0"
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | vectorDrawables.useSupportLibrary = true
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 |
30 | }
31 |
32 | dependencies {
33 | implementation fileTree(dir: 'libs', include: ['*.jar'])
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
35 |
36 | implementation 'com.android.support:appcompat-v7:26.1.0'
37 | implementation 'com.android.support:recyclerview-v7:26.1.0'
38 |
39 | implementation "android.arch.persistence.room:runtime:1.0.0"
40 | annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
41 | implementation "android.arch.lifecycle:extensions:1.1.1"
42 |
43 | //network
44 | implementation 'com.squareup.retrofit2:retrofit:2.3.0'
45 | implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
46 |
47 | //rx
48 | implementation "io.reactivex.rxjava2:rxjava:2.1.12"
49 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
50 |
51 | //Test Dependencies
52 | testImplementation 'junit:junit:4.12'
53 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
54 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
55 | implementation 'com.pddstudio:highlightjs-android:1.5.0'
56 | implementation 'com.jakewharton.timber:timber:4.6.0'
57 | }
58 | repositories {
59 | mavenCentral()
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zoomx/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.example;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.zoomx.example.model.User;
7 | import com.zoomx.example.retrofit.ApiService;
8 | import com.zoomx.example.retrofit.NetworkManager;
9 | import com.zoomx.zoomx.config.ZoomX;
10 |
11 | import java.util.List;
12 | import java.util.concurrent.TimeUnit;
13 |
14 | import io.reactivex.Observable;
15 | import io.reactivex.ObservableSource;
16 | import io.reactivex.Observer;
17 | import io.reactivex.android.schedulers.AndroidSchedulers;
18 | import io.reactivex.disposables.Disposable;
19 | import io.reactivex.functions.Function;
20 | import io.reactivex.schedulers.Schedulers;
21 |
22 | public class MainActivity extends AppCompatActivity {
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_main);
28 | startUsersService();
29 | }
30 |
31 | private void startUsersService() {
32 | NetworkManager networkManager = new NetworkManager(getApplicationContext());
33 | ApiService service = networkManager.service();
34 | service.getUsers(0, 1)
35 | .subscribeOn(Schedulers.io())
36 | .observeOn(AndroidSchedulers.mainThread())
37 | .repeatWhen((Function, ObservableSource>>) objectObservable -> objectObservable.delay(10, TimeUnit.SECONDS))
38 | .subscribe(new Observer>() {
39 | @Override
40 | public void onSubscribe(Disposable d) {
41 |
42 | }
43 |
44 | @Override
45 | public void onNext(List value) {
46 | }
47 |
48 | @Override
49 | public void onError(Throwable e) {
50 |
51 | }
52 |
53 | @Override
54 | public void onComplete() {
55 |
56 | }
57 | });
58 | }
59 |
60 | @Override
61 | protected void onPause() {
62 | super.onPause();
63 | ZoomX.hideHead();
64 | }
65 |
66 | @Override
67 | protected void onResume() {
68 | super.onResume();
69 | ZoomX.showMenu();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/SharedPreferenceManager.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.preference.PreferenceManager;
6 |
7 | /**
8 | * Created by Ibrahim AbdelGawad on 12/19/2017.
9 | */
10 |
11 | public class SharedPreferenceManager {
12 | private Context context;
13 | private SharedPreferences.Editor editor;
14 | private SharedPreferences appSharedPrefs;
15 |
16 | public SharedPreferenceManager(Context context) {
17 | this.context = context;
18 | this.appSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
19 | this.editor = this.appSharedPrefs.edit();
20 | }
21 |
22 | public boolean getBoolean(String key) {
23 | return this.getBoolean(key, false);
24 | }
25 |
26 | public boolean getBoolean(String key, boolean defaultValue) {
27 | return this.appSharedPrefs.getBoolean(key, defaultValue);
28 | }
29 |
30 | public long getLong(String key, long defaultValue) {
31 | return this.appSharedPrefs.getLong(key, defaultValue);
32 | }
33 |
34 | public long getLong(String key) {
35 | return this.appSharedPrefs.getLong(key, 0L);
36 | }
37 |
38 | public void saveBoolean(String key, boolean value) {
39 | this.editor.putBoolean(key, value).commit();
40 | }
41 |
42 | public String getString(String key) {
43 | return this.getString(key, (String) null);
44 | }
45 |
46 | public String getString(String key, String defaultValue) {
47 | return this.appSharedPrefs.getString(key, defaultValue);
48 | }
49 |
50 | public void saveString(String key, String value) {
51 | this.editor.putString(key, value).commit();
52 | }
53 |
54 | public int getInt(String key, int defaultVal) {
55 | return this.appSharedPrefs.getInt(key, defaultVal);
56 | }
57 |
58 | public void saveInt(String key, int value) {
59 | this.editor.putInt(key, value).commit();
60 | }
61 |
62 | public boolean contains(String key) {
63 | return this.appSharedPrefs.contains(key);
64 | }
65 |
66 | public void removeEntry(String key) {
67 | this.editor.remove(key).commit();
68 | }
69 |
70 | public void clearSharedPreferences() {
71 | this.appSharedPrefs.edit().clear().commit();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/notification/ScreenshotViewerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.notification
2 |
3 | import android.graphics.Bitmap
4 | import android.os.Bundle
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.View
7 | import android.widget.Button
8 | import android.widget.ImageView
9 | import android.graphics.BitmapFactory
10 | import android.os.Environment
11 | import android.widget.Toast
12 | import com.zoomx.zoomx.R
13 | import com.zoomx.zoomx.util.copyFile
14 | import java.io.*
15 | import java.util.*
16 | import java.nio.channels.FileChannel
17 |
18 |
19 | class ScreenshotViewerActivity : AppCompatActivity() {
20 | lateinit var saveButton: Button
21 | lateinit var screenshotViewer: ImageView
22 | lateinit var filePath: String
23 | lateinit var bitmap: Bitmap
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(R.layout.screenshot_viewer)
28 | initUi()
29 | loadScreenShot()
30 | }
31 |
32 | private fun deleteScreenShotAfterOpeningIt() {
33 | var file = File(filePath)
34 | file.delete()
35 | }
36 |
37 | private fun loadScreenShot() {
38 | filePath = intent.getStringExtra(ScreenShotActivity.Constants.SCREEN_SHOT_PATH)
39 | val options = BitmapFactory.Options()
40 | options.inPreferredConfig = Bitmap.Config.ARGB_8888
41 | bitmap = BitmapFactory.decodeFile(filePath, options)
42 | screenshotViewer.setImageBitmap(bitmap)
43 |
44 | }
45 |
46 |
47 | private fun initUi() {
48 | saveButton = findViewById(R.id.zoomx_save_image_button)
49 | screenshotViewer = findViewById(R.id.screenshot_viewer_imageview)
50 | saveButton.setOnClickListener { v -> saveImage() }
51 | }
52 |
53 |
54 | private fun saveImage() {
55 | val root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/ZoomX")
56 | val myDir = File(root.toString())
57 | myDir.mkdirs()
58 | val fname = "myscreen_${Date().time}.png"
59 | val file = File(myDir, fname)
60 | copyFile(File(filePath), file)
61 | Toast.makeText(this@ScreenshotViewerActivity, "screenshot saved at ${file.toString()}" , Toast.LENGTH_LONG).show()
62 | deleteScreenShotAfterOpeningIt()
63 | finish()
64 | }
65 |
66 |
67 |
68 |
69 | }
--------------------------------------------------------------------------------
/zoomx/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
18 |
19 |
23 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
41 |
44 |
45 |
46 |
50 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestdetails/RequestDetailsViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestdetails;
2 |
3 | import android.graphics.Color;
4 | import android.graphics.Typeface;
5 | import android.support.v4.content.ContextCompat;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.View;
8 | import android.widget.LinearLayout;
9 | import android.widget.TextView;
10 |
11 | import com.zoomx.zoomx.R;
12 |
13 | /**
14 | * Created by Ibrahim AbdelGawad on 12/14/2017.
15 | */
16 |
17 | public class RequestDetailsViewHolder extends RecyclerView.ViewHolder {
18 | private TextView headerKeyTextView, headerValueTextView;
19 | private LinearLayout headerLinearLayout;
20 |
21 | public RequestDetailsViewHolder(View itemView) {
22 | super(itemView);
23 | headerKeyTextView = itemView.findViewById(R.id.key_tx);
24 | headerValueTextView = itemView.findViewById(R.id.value_tx);
25 | headerLinearLayout = itemView.findViewById(R.id.header_layout);
26 | }
27 |
28 | public void bind(String header, int position) {
29 | String headerKeyAndValue[] = header.split(":");
30 | if (headerKeyAndValue.length > 1) {
31 | String key = headerKeyAndValue[0];
32 | String value = headerKeyAndValue[1];
33 |
34 | headerKeyTextView.setText(key);
35 | headerValueTextView.setText(value);
36 |
37 | if (key.equals("Response") || key.equals("Request")) {
38 | headerLinearLayout.setBackgroundColor(ContextCompat.getColor(itemView.getContext(), R.color.black));
39 | // headerKeyTextView.setAllCaps(true);
40 | // headerKeyTextView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
41 | headerValueTextView.setVisibility(View.GONE);
42 | headerKeyTextView.append(" Headers");
43 | headerKeyTextView.setAllCaps(true);
44 | headerKeyTextView.setTextColor(ContextCompat.getColor(itemView.getContext(), R.color.white));
45 | } else if (position % 2 == 0) {
46 | headerLinearLayout.setBackgroundColor(ContextCompat.getColor(itemView.getContext(), R.color.grey_100));
47 | headerValueTextView.setVisibility(View.VISIBLE);
48 | } else {
49 | headerLinearLayout.setBackgroundColor(Color.WHITE);
50 | headerValueTextView.setVisibility(View.VISIBLE);
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestdetails/JsonViewActivity.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestdetails;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.Toolbar;
6 | import android.widget.TextView;
7 |
8 | import com.pddstudio.highlightjs.HighlightJsView;
9 | import com.pddstudio.highlightjs.models.Language;
10 | import com.pddstudio.highlightjs.models.Theme;
11 | import com.zoomx.zoomx.R;
12 |
13 | import org.json.JSONArray;
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 | import org.json.JSONTokener;
17 |
18 |
19 | public class JsonViewActivity extends AppCompatActivity {
20 |
21 | Toolbar toolbar;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_body);
27 |
28 | toolbar = findViewById(R.id.toolbar);
29 | setSupportActionBar(toolbar);
30 | getSupportActionBar().setTitle(R.string.json_viewer_screen_title);
31 |
32 | if (getIntent() != null && getIntent().getExtras() != null && getIntent().getExtras().
33 | containsKey(RequestDetailsFragment.BODY_URL_KEY)) {
34 | String url = (String) getIntent().getExtras().get(RequestDetailsFragment.BODY_URL_KEY);
35 | String body = (String) getIntent().getExtras().get(RequestDetailsFragment.BODY_JSON_KEY);
36 | initUi(url, body);
37 | }
38 | }
39 |
40 | public void initUi(String url, String body) {
41 | TextView urlTextView = findViewById(R.id.body_url_txt);
42 | urlTextView.setText(url);
43 | HighlightJsView highlightJsView = (HighlightJsView) findViewById(R.id.highlight_view);
44 | //change theme and set language to auto detect
45 | highlightJsView.setTheme(Theme.MONOKAI);
46 | highlightJsView.setHighlightLanguage(Language.JSON);
47 | //load the source (can be loaded by String, File or URL)
48 | highlightJsView.setZoomSupportEnabled(false);
49 | highlightJsView.setSource(formatJsonPretty(body));
50 | }
51 |
52 | /**
53 | * Convert a JSON string to pretty version
54 | *
55 | * @param jsonString
56 | * @return
57 | */
58 | public String formatJsonPretty(String jsonString) {
59 | Object json = null;
60 | try {
61 | json = new JSONTokener(jsonString).nextValue();
62 | if (json instanceof JSONObject) {
63 | return ((JSONObject) json).toString(1);
64 | } else if (json instanceof JSONArray) {
65 | return ((JSONArray) json).toString(1);
66 | } else {
67 | return jsonString;
68 | }
69 | } catch (JSONException e) {
70 | e.printStackTrace();
71 | return "";
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Zoomx
3 | Delete All
4 | General
5 | Method
6 | Date
7 | Code
8 | URL
9 | Response
10 | Request
11 | Request Headers
12 | Response Headers
13 | Network Tracking
14 | Show menu on start
15 | Search
16 | Error
17 | share request
18 | Share File
19 | Request Details
20 | JSON Viewer
21 | Request
22 | Settings
23 | App Info
24 |
25 | Application info
26 | Package name
27 | Version Code
28 | Version name
29 | Device model
30 | Density
31 | Device info
32 | Device resolution
33 | Release
34 | API
35 |
36 | Settings
37 | Clear app data
38 | Uninstall
39 | Choose the way you want to access Network logs
40 | Zoomx UI
41 | Notification
42 | Draw over app
43 | Choose how to access Zoomx
44 | zoomx_notification_channel
45 | zoomx notification
46 | Clear
47 | Screenshot
48 |
49 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
19 |
20 |
27 |
28 |
32 |
33 |
40 |
41 |
45 |
46 |
55 |
56 |
61 |
62 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ZoomX
2 |
3 | Zoomx is android logging interceptor tool to view and log all network services history in a suitable format.
4 |
5 |
6 |
7 | [YouTube Video](https://www.youtube.com/watch?v=kri9Eyrso5M&feature=youtu.be)
8 |
9 | # Usage
10 |
11 | Gradle:
12 |
13 | Step 1. Add the JitPack repository to your build file
14 |
15 | Add it in your root build.gradle at the end of repositories:
16 |
17 | ```gradle
18 | allprojects {
19 | repositories {
20 | maven { url 'https://jitpack.io' }
21 | }
22 | }
23 | ```
24 |
25 | Step 2. Add the dependency [ make sure that you are on the latest release number ]
26 |
27 | ```gradle
28 | dependencies {
29 | implementation 'com.github.district0:ZoomX:1.0.8'
30 | ```
31 |
32 | Step 3. Initiate ZoomX service
33 | ```java
34 | ZoomX.init(new Config.Builder(this).build());
35 | ```
36 |
37 | Step 4. Start/Stop ZoomX service. You can call it in your app activity cycles like (OnResume/OnPause)
38 | ```java
39 | ZoomX.showMenu();
40 | ZoomX.hideHead();
41 | ```
42 |
43 | Step 5. Log your network requests
44 |
45 | # Retrofit
46 | Just add ZoomXLoggerInterceptor:
47 | ```java
48 | OkHttpClient.Builder httpClient = new OkHttpClient
49 | .Builder()
50 | .addInterceptor(new ZoomXLoggerInterceptor(this.context));
51 | ```
52 |
53 | # Volley or any request method
54 |
55 | Just create zoomx request entitiy object then send it to zoomx network log manager.
56 |
57 | For example:
58 |
59 | ```java
60 | RequestEntity.Builder requestBuilder = new RequestEntity.Builder();
61 |
62 | requestBuilder.setMethod("GET")
63 | .setCode(200)
64 | .setStartDate("Start-Date")
65 | .setUrl("https://github.com/district0/ZoomX")
66 | .setRequestBody("JSON_BODY")
67 | .setRequestHeaders("HEADERS")
68 | .setResponseBody(response);
69 |
70 | ZoomXLogManager.log(requestBuilder);
71 | ```
72 |
73 | # Features
74 | - Display list of requests in real time sorted by date.
75 | - Send logged request/response by email.
76 | - Search within requests url.
77 | - Copy any response/requests and share it via any app ex. (email).
78 | - Display request details in a pretty format.
79 | - Take screenshot
80 |
81 | # Upcoming Features
82 | - Group requests per page.
83 | - Memory leaks report per page and whole app.
84 | - Control internet speed.
85 | - Shake to change working environment.
86 | - Badge title (To indicate user to on which environment you are connected production, staging,...).
87 | - Crashes report.
88 |
89 | # Donation
90 | If this project help you reduce time to develop, you can give me a cup of coffee :)
91 |
92 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=N73ELFCKSYKPQ&source=url)
93 |
94 |
95 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/ShakeEventManager.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.content.Context;
4 | import android.hardware.Sensor;
5 | import android.hardware.SensorEvent;
6 | import android.hardware.SensorEventListener;
7 | import android.hardware.SensorManager;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * Created by Ahmed Fathallah on 2/6/2018.
13 | */
14 |
15 | public class ShakeEventManager implements SensorEventListener {
16 |
17 | private SensorManager mSensorManager;
18 | private Context context;
19 | private Sensor mAccelerometer;
20 | private OnShakeEventListener mOnShakeEventListener;
21 | private long lastUpdateTime;
22 |
23 | public ShakeEventManager(Context context) {
24 | this.context = context;
25 | }
26 |
27 | public void listen(OnShakeEventListener onShakeEventListener) {
28 | mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
29 | if (!checkIfAccelerometerExist()) {
30 | throw new IllegalStateException("TYPE_ACCELEROMETER sensor is not available on this device");
31 | }
32 | this.mOnShakeEventListener = onShakeEventListener;
33 | mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
34 | mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
35 | lastUpdateTime = System.currentTimeMillis();
36 | }
37 |
38 | private boolean checkIfAccelerometerExist() {
39 | List listOfSensorsOnDevice = mSensorManager.getSensorList(Sensor.TYPE_ALL);
40 | for (int i = 0; i < listOfSensorsOnDevice.size(); i++) {
41 | if (listOfSensorsOnDevice.get(i).getType() == Sensor.TYPE_ACCELEROMETER) {
42 | return true;
43 | }
44 | }
45 | return false;
46 | }
47 |
48 | @Override
49 | public void onSensorChanged(SensorEvent event) {
50 | if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
51 | float[] values = event.values;
52 | float x = values[0];
53 | float y = values[1];
54 | float z = values[2];
55 | float accelerationSqt = (x * x + y * y + z * z)
56 | / (SensorManager.GRAVITY_EARTH * SensorManager.GRAVITY_EARTH);
57 | long actualTime = event.timestamp;
58 | if (accelerationSqt >= 2) {
59 | if (actualTime - lastUpdateTime < 200) {
60 | return;
61 | }
62 | lastUpdateTime = actualTime;
63 | if (mOnShakeEventListener != null) {
64 | mOnShakeEventListener.OnShakeDetected();
65 | }
66 | }
67 | }
68 |
69 | }
70 |
71 | @Override
72 | public void onAccuracyChanged(Sensor sensor, int accuracy) {
73 |
74 | }
75 |
76 | public void release() {
77 | mOnShakeEventListener = null;
78 | mSensorManager.unregisterListener(this);
79 | // mSensorManager = null;
80 | // mAccelerometer = null;
81 | }
82 |
83 | public interface OnShakeEventListener {
84 | void OnShakeDetected();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestlist/RequestAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestlist;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.text.TextUtils;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Filter;
9 | import android.widget.Filterable;
10 |
11 | import com.zoomx.zoomx.R;
12 | import com.zoomx.zoomx.model.RequestEntity;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by Ibrahim AbdelGawad on 12/3/2017.
19 | */
20 |
21 | public class RequestAdapter extends RecyclerView.Adapter implements Filterable {
22 |
23 | private List requests = new ArrayList<>();
24 | private OnRequestItemClickListener onRequestItemClickListener;
25 | private List mFilteredList;
26 |
27 | public RequestAdapter() {
28 | }
29 |
30 | public void setRequestEntityList(List requestEntityList, OnRequestItemClickListener
31 | onRequestItemClickListener) {
32 | this.requests = requestEntityList;
33 | mFilteredList = requestEntityList;
34 | this.onRequestItemClickListener = onRequestItemClickListener;
35 | notifyDataSetChanged();
36 | }
37 |
38 | @Override
39 | public RequestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
40 | View view = LayoutInflater.from(parent.getContext())
41 | .inflate(R.layout.request_item_row, parent, false);
42 |
43 | return new RequestViewHolder(view);
44 | }
45 |
46 | @Override
47 | public void onBindViewHolder(RequestViewHolder holder, int position) {
48 | holder.bind(this.mFilteredList.get(position), onRequestItemClickListener);
49 | }
50 |
51 | @Override
52 | public int getItemCount() {
53 | if (mFilteredList == null)
54 | return 0;
55 | return mFilteredList.size();
56 | }
57 |
58 | @Override
59 | public Filter getFilter() {
60 | return new Filter() {
61 | @Override
62 | protected FilterResults performFiltering(CharSequence constraint) {
63 | List filteredList = new ArrayList<>();
64 |
65 | if (TextUtils.isEmpty(constraint)) {
66 | filteredList = requests;
67 | } else {
68 | for (RequestEntity request : requests) {
69 | if (request.toString().toLowerCase().contains(constraint.toString().toLowerCase())) {
70 | filteredList.add(request);
71 | }
72 | }
73 | }
74 |
75 | FilterResults filterResults = new FilterResults();
76 | filterResults.values = filteredList;
77 | return filterResults;
78 | }
79 |
80 | @Override
81 | protected void publishResults(CharSequence constraint, FilterResults results) {
82 | mFilteredList = (List) results.values;
83 | notifyDataSetChanged();
84 | }
85 | };
86 | }
87 |
88 | public interface OnRequestItemClickListener {
89 | void onItemClick(RequestEntity requestEntity);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/settings/SettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.settings
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.support.v7.app.AlertDialog
7 | import android.support.v7.app.AppCompatActivity
8 | import android.support.v7.widget.SwitchCompat
9 | import android.support.v7.widget.Toolbar
10 | import android.widget.CompoundButton
11 | import android.widget.RadioGroup
12 | import com.zoomx.zoomx.R
13 | import com.zoomx.zoomx.ui.ZoomxUIOption
14 | import kotlinx.android.synthetic.main.activity_settings.*
15 |
16 | class SettingActivity : AppCompatActivity(), CompoundButton.OnCheckedChangeListener {
17 |
18 | private lateinit var toolbar: Toolbar
19 | private lateinit var mSettingsManager: SettingsManager
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_settings)
24 | initUi()
25 | }
26 |
27 | private fun initUi() {
28 | mSettingsManager = SettingsManager[this]
29 | initToolBar()
30 | initSwitch(R.id.setting_network_tracker_switch, mSettingsManager.isNetworkTrackingEnabled!!)
31 | initZoomxUiOptions()
32 | }
33 |
34 | private fun initSwitch(id: Int, isChecked: Boolean) {
35 | val switchCompat = findViewById(id)
36 | switchCompat.setOnCheckedChangeListener(this)
37 | switchCompat.isChecked = isChecked
38 | }
39 |
40 | private fun initToolBar() {
41 | toolbar = findViewById(R.id.toolbar)
42 | setSupportActionBar(toolbar)
43 | supportActionBar?.setTitle(R.string.setting_screen_title)
44 | }
45 |
46 | private fun initZoomxUiOptions() {
47 |
48 | zoomx_ui_options.setOnClickListener {
49 |
50 | val view = layoutInflater.inflate(R.layout.zoomx_ui_options_checkboxes, null, false)
51 |
52 | val defaultChecked = if (mSettingsManager.zoomxUIOption == ZoomxUIOption.NOTIFICATION)
53 | R.id.radio_notification_option else R.id.radio_draw_over_apps_option
54 |
55 | with(view.findViewById(R.id.zoomx_ui_options_radios)) {
56 | check(defaultChecked)
57 | setOnCheckedChangeListener { _, checkedId ->
58 | mSettingsManager.zoomxUIOption = if (checkedId == R.id.radio_notification_option)
59 | ZoomxUIOption.NOTIFICATION else ZoomxUIOption.DRAW_OVER_APPS
60 | }
61 | }
62 | val dialog: AlertDialog = AlertDialog
63 | .Builder(this)
64 | .setTitle(getString(R.string.zoomx_ui_chooser))
65 | .setView(view)
66 | .create()
67 |
68 | dialog.show()
69 | }
70 | }
71 |
72 | override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
73 | val id = buttonView.id
74 | if (id == R.id.setting_network_tracker_switch) {
75 | mSettingsManager.setNetworkTrackingStatus(isChecked)
76 | }
77 | }
78 |
79 | companion object {
80 | @JvmStatic
81 | fun start(context: Context) {
82 | val intent = Intent(context, SettingActivity::class.java).apply {
83 | flags = Intent.FLAG_ACTIVITY_NEW_TASK
84 | }
85 | context.startActivity(intent)
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/info/InfoSectionView.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.info;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.widget.FrameLayout;
10 | import android.widget.LinearLayout;
11 | import android.widget.TextView;
12 |
13 | import com.zoomx.zoomx.R;
14 | import com.zoomx.zoomx.model.InfoModel;
15 |
16 | import java.util.List;
17 |
18 | /**
19 | * Created by Ahmed Fathallah on 2/11/2018.
20 | */
21 |
22 | public class InfoSectionView extends FrameLayout {
23 |
24 |
25 | private LinearLayout baseView;
26 | private TextView sectionTitleView;
27 |
28 | public InfoSectionView(@NonNull Context context) {
29 | super(context);
30 | initUI();
31 | }
32 |
33 | public InfoSectionView(@NonNull Context context, @Nullable AttributeSet attrs) {
34 | super(context, attrs);
35 | initUI();
36 | }
37 |
38 | public InfoSectionView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
39 | super(context, attrs, defStyleAttr);
40 | initUI();
41 | }
42 |
43 | private void initUI() {
44 | View view = inflate(getContext(), R.layout.view_info_section, this);
45 | baseView = view.findViewById(R.id.view_info_section);
46 | sectionTitleView = baseView.findViewById(R.id.info_section_title);
47 | }
48 |
49 | public void setSectionTitle(String title) {
50 | sectionTitleView.setText(title);
51 | }
52 |
53 | public void setInfoModels(List infoModels) {
54 | if (infoModels == null)
55 | return;
56 | for (InfoModel model : infoModels) {
57 | if (model.isTextViewType()) {
58 | baseView.addView(createViewWithText(model));
59 | } else if (model.isButtonType()) {
60 | baseView.addView(createViewWithButton(model));
61 | } else if (model.isSwitchType()) {
62 | baseView.addView(createViewWithSwitch(model));
63 | }
64 | }
65 | }
66 |
67 | private View createViewWithText(InfoModel model) {
68 | View view = LayoutInflater.from(getContext()).inflate(R.layout.info_text_view, null);
69 | TextView title = view.findViewById(R.id.info_text_title_tv);
70 | TextView value = view.findViewById(R.id.info_text_value_tv);
71 | title.setText(model.getTitle());
72 | value.setText(model.getValue());
73 | return view;
74 | }
75 |
76 | private View createViewWithButton(final InfoModel model) {
77 | View view = LayoutInflater.from(getContext()).inflate(R.layout.info_text_view, null);
78 | TextView title = view.findViewById(R.id.info_text_title_tv);
79 | TextView value = view.findViewById(R.id.info_text_value_tv);
80 | title.setText(model.getTitle());
81 | value.setVisibility(GONE);
82 | view.setOnClickListener(new OnClickListener() {
83 | @Override
84 | public void onClick(View v) {
85 | if (model.getButtonAction() != null) {
86 | model.getButtonAction().run();
87 | }
88 | }
89 | });
90 | return view;
91 | }
92 |
93 | private View createViewWithSwitch(InfoModel model) {
94 | return null;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/config/ZoomX.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.config;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.support.annotation.NonNull;
5 | import android.util.Log;
6 |
7 | import com.zoomx.zoomx.db.AppDatabase;
8 | import com.zoomx.zoomx.db.RequestDao;
9 | import com.zoomx.zoomx.ui.ZoomxUIOption;
10 | import com.zoomx.zoomx.ui.menu.ZoomxMenuService;
11 | import com.zoomx.zoomx.ui.notification.ZoomxNotification;
12 | import com.zoomx.zoomx.ui.settings.SettingActivity;
13 | import com.zoomx.zoomx.ui.settings.SettingsManager;
14 | import com.zoomx.zoomx.util.ServiceUtils;
15 | import com.zoomx.zoomx.util.ShakeEventManager;
16 |
17 |
18 | /**
19 | * Created by Ahmed Fathallah on 12/10/2017.
20 | */
21 |
22 | public final class ZoomX {
23 |
24 | private static final String TAG = "ZoomX:Manager";
25 |
26 | @SuppressLint("StaticFieldLeak")
27 | private static Config config;
28 | private static RequestDao requestDao;
29 | private static ShakeEventManager mShakeEventManager;
30 | private static ZoomxNotification zoomxNotification;
31 | private static SettingsManager settingsManager;
32 |
33 | public static void init(Config config) {
34 | ZoomX.config = config;
35 | checkPreConditions();
36 | setupDataBase();
37 | handleShowMenuOnStart();
38 | handleShowMenuOnShakeEvent();
39 | }
40 |
41 | private static void checkPreConditions() {
42 | if (config.canShowOnShakeEvent() && config.canShowMenuOnAppStart()) {
43 | Log.d(TAG, "You should only enable show On shake or on app start");
44 | }
45 | }
46 |
47 | private static void handleShowMenuOnStart() {
48 | if (config.canShowMenuOnAppStart() && !config.canShowOnShakeEvent()
49 | && config.getZoomxUIOption() != ZoomxUIOption.NOTIFICATION) {
50 | showMenu();
51 | }
52 | }
53 |
54 | private static void showSettingsActivity() {
55 | SettingActivity.start(config.getContext());
56 | }
57 |
58 | private static void setupDataBase() {
59 | AppDatabase database = AppDatabase.get(config.getContext());
60 | requestDao = database.requestDao();
61 | }
62 |
63 | public static void showMenu() {
64 | if (!ServiceUtils.isMyServiceRunning(ZoomxMenuService.class, config.getContext())) {
65 | ZoomxMenuService.showMenuHead(config.getContext());
66 | }
67 | }
68 |
69 | public static void hideHead() {
70 | ZoomxMenuService.hideMenuHead(config.getContext());
71 | }
72 |
73 | public static void handleShowMenuOnShakeEvent() {
74 | Log.d(TAG, "Show Menu on shake called");
75 | if (config.canShowOnShakeEvent() && !config.canShowMenuOnAppStart()) {
76 | Log.d(TAG, "Show Menu on shake executed");
77 | mShakeEventManager = new ShakeEventManager(config.getContext());
78 | mShakeEventManager.listen(() -> {
79 | Log.d(TAG, "shake detected");
80 | showMenu();
81 | });
82 | }
83 | }
84 |
85 |
86 | public static SettingsManager getSettingsManager() {
87 | if (settingsManager == null) {
88 | settingsManager = SettingsManager.get(config.getContext());
89 | }
90 | return settingsManager;
91 | }
92 |
93 | public static ZoomxNotification getNotification() {
94 | if (zoomxNotification == null) {
95 | zoomxNotification = new ZoomxNotification(config.getContext());
96 | }
97 | return zoomxNotification;
98 | }
99 |
100 |
101 | @NonNull
102 | public static RequestDao getRequestDao() {
103 | return requestDao;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/view_main_actions_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
27 |
28 |
40 |
41 |
53 |
54 |
66 |
78 |
79 |
80 |
86 |
87 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/info/InfoFragment.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.info;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v4.app.Fragment;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | import com.zoomx.zoomx.R;
11 | import com.zoomx.zoomx.model.InfoModel;
12 | import com.zoomx.zoomx.util.PhoneUtils;
13 |
14 | import java.util.ArrayList;
15 |
16 | /**
17 | * Created by Ahmed Fathallah on 2/17/2018.
18 | */
19 |
20 | public class InfoFragment extends Fragment {
21 |
22 | private View view;
23 |
24 | @Nullable
25 | @Override
26 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
27 | view = inflater.inflate(R.layout.info_fragment, container, false);
28 | initUI();
29 | return view;
30 | }
31 |
32 | private void initUI() {
33 | ((InfoActivity) getActivity()).getSupportActionBar().setTitle(R.string.info_screen_title);
34 |
35 | InfoSectionView appInfo = view.findViewById(R.id.info_app_view);
36 | appInfo.setInfoModels(createAppInfo());
37 | appInfo.setSectionTitle(getString(R.string.info_app_section_title));
38 |
39 | InfoSectionView deviceInfo = view.findViewById(R.id.info_device_view);
40 | deviceInfo.setInfoModels(createDeviceInfo());
41 | deviceInfo.setSectionTitle(getString(R.string.info_device_section_title));
42 |
43 | InfoSectionView actions = view.findViewById(R.id.info_actions_view);
44 | actions.setInfoModels(createSettings());
45 | actions.setSectionTitle(getString(R.string.info_settings_section_title));
46 | }
47 |
48 | private ArrayList createSettings() {
49 | ArrayList infoModels = new ArrayList<>();
50 | InfoModel model = new InfoModel(InfoModel.BUTTON_VIEW_TYPE, getString(R.string.info_clear_app_data), null, new Runnable() {
51 | @Override
52 | public void run() {
53 | PhoneUtils.clearAppData(getActivity());
54 | }
55 | });
56 | infoModels.add(model);
57 | model = new InfoModel(InfoModel.BUTTON_VIEW_TYPE, getString(R.string.info_uninstall), null, new Runnable() {
58 | @Override
59 | public void run() {
60 | PhoneUtils.uninstall(getActivity());
61 | }
62 | });
63 | infoModels.add(model);
64 | return infoModels;
65 | }
66 |
67 |
68 | private ArrayList createDeviceInfo() {
69 | ArrayList infoModels = new ArrayList<>();
70 | InfoModel model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_device_model), PhoneUtils.getDeviceModel());
71 | infoModels.add(model);
72 | model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_device_density), PhoneUtils.getDensity(getContext()));
73 | infoModels.add(model);
74 | model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_device_resolution), PhoneUtils.getDeviceResolution(getContext()));
75 | infoModels.add(model);
76 | model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_release), PhoneUtils.getAndroidRelease());
77 | infoModels.add(model);
78 | model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_api), String.valueOf(PhoneUtils.getAndroidApi()));
79 | infoModels.add(model);
80 | return infoModels;
81 | }
82 |
83 | private ArrayList createAppInfo() {
84 | ArrayList infoModels = new ArrayList<>();
85 | InfoModel model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_package_name), PhoneUtils.getPackageName(getContext()));
86 | infoModels.add(model);
87 | model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_version_name), PhoneUtils.getAppVersionName(getContext()));
88 | infoModels.add(model);
89 | model = new InfoModel(InfoModel.TEXT_VIEW_TYPE, getString(R.string.info_version_code), String.valueOf(PhoneUtils.getAppVersion(getContext())));
90 | infoModels.add(model);
91 | return infoModels;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/util/PhoneUtils.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.util;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.os.Build;
8 | import android.telephony.TelephonyManager;
9 | import android.util.DisplayMetrics;
10 |
11 | import static android.content.Context.ACTIVITY_SERVICE;
12 |
13 |
14 | public class PhoneUtils {
15 |
16 | public static int getAppVersion(Context context) {
17 | int appVersion = 0;
18 | try {
19 | appVersion = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
20 | } catch (Exception ex) {
21 |
22 | }
23 | return appVersion;
24 | }
25 |
26 | public static String getPackageName(Context context) {
27 | return context.getApplicationContext().getPackageName();
28 | }
29 |
30 | public static String getAppVersionName(Context context) {
31 | String appVersionName = "";
32 | try {
33 | appVersionName = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
34 | } catch (Exception ex) {
35 |
36 | }
37 | return appVersionName;
38 | }
39 |
40 | public static String getPhoneSerial(Context context) {
41 | TelephonyManager mTelephonyMgr = ((TelephonyManager) context
42 | .getSystemService(Context.TELEPHONY_SERVICE));
43 | String imei = mTelephonyMgr.getDeviceId();
44 | return imei;
45 | }
46 |
47 | public static String getDeviceModel() {
48 | String manufacturer = Build.MANUFACTURER;
49 | String model = Build.MODEL;
50 | if (model.startsWith(manufacturer)) {
51 | return model;
52 | } else {
53 | return manufacturer + " " + model;
54 | }
55 | }
56 |
57 | public static String getDensity(Context context) {
58 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
59 | switch (displayMetrics.densityDpi) {
60 | case DisplayMetrics.DENSITY_LOW:
61 | return "ldpi";
62 | case DisplayMetrics.DENSITY_MEDIUM:
63 | return "mdpi";
64 | case DisplayMetrics.DENSITY_HIGH:
65 | return "hdpi";
66 | case DisplayMetrics.DENSITY_XHIGH:
67 | return "xhdpi";
68 | case DisplayMetrics.DENSITY_XXHIGH:
69 | return "xxhdpi";
70 | case DisplayMetrics.DENSITY_XXXHIGH:
71 | return "xxxhdpi";
72 | case DisplayMetrics.DENSITY_TV:
73 | return "tvdpi";
74 | default:
75 | return String.valueOf(displayMetrics.densityDpi);
76 | }
77 | }
78 |
79 | public static String getDeviceResolution(Context context) {
80 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
81 | int width = displayMetrics.widthPixels;
82 | int height = displayMetrics.heightPixels;
83 | return width + " X " + height;
84 | }
85 |
86 | public static String getAndroidRelease() {
87 | return Build.VERSION.RELEASE;
88 | }
89 |
90 | public static int getAndroidApi() {
91 | return Build.VERSION.SDK_INT;
92 | }
93 |
94 | public static void clearAppData(Context context) {
95 | if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) {
96 | ActivityManager manager = ((ActivityManager) context.getSystemService(ACTIVITY_SERVICE));
97 | if (manager != null) {
98 | manager.clearApplicationUserData();
99 | }
100 | } else {
101 | try {
102 | // clearing app data
103 | Runtime runtime = Runtime.getRuntime();
104 | runtime.exec("pm clear YOUR_APP_PACKAGE_GOES HERE");
105 |
106 | } catch (Exception e) {
107 | e.printStackTrace();
108 | }
109 | }
110 | }
111 |
112 | public static void uninstall(Context context) {
113 | Uri packageURI = Uri.parse("package:" + context.getPackageName());
114 | Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
115 | context.startActivity(uninstallIntent);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/networklogger/ZoomXLoggerInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.networklogger;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 |
6 | import com.zoomx.zoomx.config.ZoomX;
7 | import com.zoomx.zoomx.model.RequestEntity;
8 | import com.zoomx.zoomx.ui.ZoomxUIOption;
9 | import com.zoomx.zoomx.ui.settings.SettingsManager;
10 | import com.zoomx.zoomx.util.FormatUtil;
11 |
12 | import java.io.IOException;
13 | import java.nio.charset.Charset;
14 | import java.util.ArrayList;
15 | import java.util.Date;
16 |
17 | import okhttp3.Headers;
18 | import okhttp3.Interceptor;
19 | import okhttp3.MediaType;
20 | import okhttp3.Protocol;
21 | import okhttp3.Request;
22 | import okhttp3.RequestBody;
23 | import okhttp3.Response;
24 | import okhttp3.ResponseBody;
25 | import okio.Buffer;
26 | import okio.BufferedSource;
27 |
28 | /**
29 | * Created by Ahmed Fathallah on 11/19/2017.
30 | */
31 |
32 | public class ZoomXLoggerInterceptor implements Interceptor {
33 |
34 | private static final Charset UTF_8 = Charset.forName("utf-8");
35 | private Context context;
36 |
37 | public ZoomXLoggerInterceptor(Context context) {
38 | this.context = context;
39 | checkDrawOverAppsOption(context);
40 | }
41 |
42 | private void checkDrawOverAppsOption(Context context) {
43 | if (SettingsManager.get(context).getZoomxUIOption() == ZoomxUIOption.DRAW_OVER_APPS)
44 | ZoomX.showMenu();
45 | }
46 |
47 | @Override
48 | public Response intercept(@NonNull Chain chain) {
49 | try {
50 | RequestEntity.Builder requestBuilder = new RequestEntity.Builder();
51 | Response response = null;
52 |
53 | if (!SettingsManager.get(context).isNetworkTrackingEnabled()) {
54 | return chain.proceed(chain.request());
55 | }
56 | Request retrofitRequest = chain.request();
57 | RequestBody requestBody = retrofitRequest.body();
58 |
59 | boolean hasRequestBody = requestBody != null;
60 |
61 | requestBuilder.setMethod(retrofitRequest.method())
62 | .setUrl(retrofitRequest.url().toString())
63 | .setRequestBody(hasRequestBody ? FormatUtil.bodyToString(requestBody) : "")
64 | .setRequestHeaders(getHeaders(retrofitRequest.headers()));
65 |
66 | long startDateInMs = System.currentTimeMillis();
67 | response = chain.proceed(retrofitRequest);
68 |
69 | long timeTookInMs = System.currentTimeMillis() - startDateInMs;
70 | requestBuilder.setStartDate(new Date(startDateInMs));
71 | requestBuilder.setTookTime(timeTookInMs);
72 |
73 | if (response != null) {
74 | requestBuilder.setResponseHeaders(getHeaders(response.headers()));
75 | requestBuilder.setCode(response.code());
76 | requestBuilder.setResponseBody(responseBody(response));
77 | }
78 |
79 | ZoomXLogManager.log(requestBuilder);
80 | return response;
81 | } catch (Exception e) {
82 | e.printStackTrace();
83 | return new Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_0).message("No Internet Connection")
84 | .body(ResponseBody.create(MediaType.parse("text/plain"), "")).code(503).build();
85 | }
86 | }
87 |
88 | private ArrayList getHeaders(Headers headers) {
89 | ArrayList headersMap = new ArrayList<>();
90 | if (headers != null) {
91 | for (int i = 0; i < headers.size(); i++) {
92 | headersMap.add(headers.name(i) + ": " + headers.value(i));
93 | }
94 | }
95 | return headersMap;
96 | }
97 |
98 | private String responseBody(Response response) throws IOException {
99 | ResponseBody responseBody = response.body();
100 | String body = "";
101 | if (responseBody != null) {
102 | BufferedSource source = responseBody.source();
103 | source.request(Long.MAX_VALUE);
104 | Buffer buffer = source.buffer();
105 |
106 | Charset charset = UTF_8;
107 | MediaType contentType = responseBody.contentType();
108 | if (contentType != null) {
109 | charset = contentType.charset(UTF_8);
110 | }
111 | body = buffer.clone().readString(charset);
112 | }
113 | return body;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestlist/RequestListFragment.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestlist;
2 |
3 | import android.app.SearchManager;
4 | import android.arch.lifecycle.Observer;
5 | import android.arch.lifecycle.ViewModelProviders;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.graphics.Color;
9 | import android.os.Bundle;
10 | import android.support.annotation.Nullable;
11 | import android.support.v4.app.Fragment;
12 | import android.support.v7.widget.DefaultItemAnimator;
13 | import android.support.v7.widget.LinearLayoutManager;
14 | import android.support.v7.widget.RecyclerView;
15 | import android.support.v7.widget.SearchView;
16 | import android.view.*;
17 | import com.zoomx.zoomx.R;
18 | import com.zoomx.zoomx.model.RequestEntity;
19 | import com.zoomx.zoomx.ui.requestdetails.RequestDetailsActivity;
20 |
21 | import java.util.List;
22 |
23 | /**
24 | * Created by Ahmed Fathallah on 2/17/2018.
25 | */
26 |
27 | public class RequestListFragment extends Fragment implements SearchView.OnQueryTextListener, RequestAdapter.OnRequestItemClickListener {
28 |
29 | public static final String REQUEST_ID = "requestId";
30 | private RequestAdapter requestAdapter;
31 | private RequestListViewModel viewModel;
32 | private SearchView searchView;
33 |
34 | @Nullable
35 | @Override
36 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
37 | View view = inflater.inflate(R.layout.request_list_fragment, container, false);
38 | setHasOptionsMenu(true);
39 | ((RequestActivity) getActivity()).getSupportActionBar().setTitle(R.string.request_screen_title);
40 | RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
41 | requestAdapter = new RequestAdapter();
42 | RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getContext());
43 | recyclerView.setLayoutManager(mLayoutManager);
44 | recyclerView.setItemAnimator(new DefaultItemAnimator());
45 | recyclerView.setAdapter(requestAdapter);
46 |
47 | setRequestData();
48 |
49 | return view;
50 | }
51 |
52 | public void setRequestData() {
53 | viewModel = ViewModelProviders.of(this).get(RequestListViewModel.class);
54 | viewModel.getRequests().observe(this, new Observer>() {
55 | @Override
56 | public void onChanged(@Nullable List requestEntities) {
57 | if (searchView != null && searchView.isIconified())
58 | requestAdapter.setRequestEntityList(requestEntities, RequestListFragment.this);
59 | }
60 | });
61 | }
62 |
63 | @Override
64 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
65 | inflater.inflate(R.menu.menu_request_list, menu);
66 | prepareSearchView(menu);
67 | }
68 |
69 | private void prepareSearchView(Menu menu) {
70 | // Associate searchable configuration with the SearchView
71 | SearchManager searchManager =
72 | (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE);
73 | searchView =
74 | (SearchView) menu.findItem(R.id.action_search_requests).getActionView();
75 | searchView.setSearchableInfo(
76 | searchManager.getSearchableInfo(getActivity().getComponentName()));
77 | searchView.setSubmitButtonEnabled(true);
78 | searchView.setOnQueryTextListener(this);
79 | searchView.setBackgroundColor(Color.WHITE);
80 | /* Code for changing the textcolor and hint color for the search view */
81 | SearchView.SearchAutoComplete searchAutoComplete =
82 | (SearchView.SearchAutoComplete) searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text);
83 | searchAutoComplete.setTextColor(Color.GRAY);
84 | searchAutoComplete.setHintTextColor(Color.GRAY);
85 | }
86 |
87 | @Override
88 | public boolean onOptionsItemSelected(MenuItem item) {
89 | if (item.getItemId() == R.id.action_delete_requests) {
90 | viewModel.clearRequestFromDB().subscribe();
91 | return true;
92 | }
93 | return super.onOptionsItemSelected(item);
94 | }
95 |
96 | @Override
97 | public void onItemClick(RequestEntity requestEntity) {
98 | Intent intent = new Intent(getActivity(), RequestDetailsActivity.class);
99 | intent.putExtra(REQUEST_ID, requestEntity.getId());
100 | startActivity(intent);
101 | }
102 |
103 | @Override
104 | public boolean onQueryTextSubmit(String query) {
105 | return false;
106 | }
107 |
108 | @Override
109 | public boolean onQueryTextChange(String newText) {
110 | requestAdapter.getFilter().filter(newText);
111 | return true;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/notification/ZoomxNotification.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.notification
2 |
3 | import android.app.NotificationChannel
4 | import android.app.NotificationManager
5 | import android.app.PendingIntent
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.net.Uri
9 | import android.os.Build
10 | import android.support.v4.app.NotificationCompat
11 | import android.support.v4.app.NotificationManagerCompat
12 | import com.zoomx.zoomx.R
13 | import com.zoomx.zoomx.config.ZoomX
14 | import com.zoomx.zoomx.model.RequestEntity
15 | import com.zoomx.zoomx.ui.requestlist.RequestActivity
16 | import com.zoomx.zoomx.ui.settings.SettingActivity
17 |
18 | /**
19 | *
20 | * Created by Mohamed Ibrahim on 12/8/18.
21 | */
22 | class ZoomxNotification(private val context: Context) {
23 |
24 | private val notificationBuilder: NotificationCompat.Builder
25 | private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context)
26 | private val buffer = Buffer()
27 | private var counter: Int = 0
28 |
29 | private class Buffer(list: MutableList = mutableListOf()) : MutableList by list {
30 | operator fun plusAssign(string: String) {
31 | add(string)
32 | if (size > BUFFER_SIZE) {
33 | removeAt(0)
34 | }
35 |
36 | }
37 | }
38 |
39 |
40 | init {
41 | val intent = Intent(context, RequestActivity::class.java).apply {
42 | flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
43 | }
44 | val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
45 | notificationBuilder = with(NotificationCompat.Builder(context, ZOOMX_NOTIFICATON_CHANNEL)) {
46 | setContentTitle("Zoomx")
47 | setSubText("Expand to see logs`")
48 | setSmallIcon(R.drawable.ic_swap_vert_black_24dp)
49 | priority = NotificationCompat.PRIORITY_DEFAULT
50 | setContentIntent(pendingIntent)
51 | addAction(getClearAction())
52 | addAction(getSettingsAction())
53 | addAction(getTakeScreenShotAction())
54 | setAutoCancel(false)
55 | }
56 | }
57 |
58 |
59 | fun show(requestEntity: RequestEntity) {
60 |
61 | val inboxStyle = NotificationCompat.InboxStyle()
62 | with(notificationManager) {
63 | val body = """
64 | ${requestEntity.method}||${requestEntity.code}
65 | --> ${Uri.parse(requestEntity.url).path}
66 | """.trimIndent()
67 |
68 | buffer += body
69 | counter++
70 | buffer.reversed().forEachIndexed { index, _ ->
71 | inboxStyle.addLine(buffer[index])
72 | notificationBuilder.setStyle(inboxStyle)
73 | }
74 |
75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
76 | notificationBuilder.setSubText(counter.toString())
77 | } else {
78 | notificationBuilder.setNumber(counter)
79 | }
80 | notify(ZOOMX_NOTIFICATION_ID, notificationBuilder.build())
81 | }
82 | }
83 |
84 |
85 | private fun getClearAction(): NotificationCompat.Action {
86 | val clearTitle = context.getString(R.string.clear_logs)
87 | val deleteIntent = Intent(context, ClearLogsIntentService::class.java)
88 | val intent = PendingIntent.getService(context, 11, deleteIntent, PendingIntent.FLAG_ONE_SHOT)
89 | return NotificationCompat.Action(R.drawable.ic_delete_black_24dp, clearTitle, intent)
90 | }
91 |
92 | private fun getSettingsAction(): NotificationCompat.Action {
93 | val clearTitle = context.getString(R.string.setting_screen_title)
94 | val settingsAction = Intent(context, SettingActivity::class.java)
95 | val intent = PendingIntent.getActivity(context, 0, settingsAction, 0)
96 | return NotificationCompat.Action(R.drawable.ic_settings_black_24dp, clearTitle, intent)
97 | }
98 | private fun getTakeScreenShotAction(): NotificationCompat.Action {
99 | val clearTitle = context.getString(R.string.screen_shot_title)
100 | val screenshotAction = Intent(context, ScreenShotActivity::class.java)
101 | val intent = PendingIntent.getActivity(context, 0, screenshotAction, 0)
102 | return NotificationCompat.Action(android.R.drawable.sym_def_app_icon, clearTitle, intent)
103 | }
104 | fun clear() {
105 | counter = 0
106 | ZoomX.getRequestDao().clearRequestsData()
107 | buffer.clear()
108 | notificationManager.cancel(ZOOMX_NOTIFICATION_ID)
109 |
110 | }
111 |
112 |
113 | companion object {
114 | private const val ZOOMX_NOTIFICATON_CHANNEL: String = "Zoomx_notification"
115 | private const val ZOOMX_NOTIFICATION_ID = 1023
116 | private const val BUFFER_SIZE = 5
117 |
118 | @JvmStatic
119 | fun createNotificationChannel(context: Context) {
120 |
121 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
122 | val name = context.getString(R.string.channel_name)
123 | val descriptionText = context.getString(R.string.channel_description)
124 | val importance = NotificationManager.IMPORTANCE_DEFAULT
125 | val channel = NotificationChannel(ZOOMX_NOTIFICATON_CHANNEL, name, importance).apply {
126 | description = descriptionText
127 | }
128 | val notificationManager: NotificationManager =
129 | context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
130 | notificationManager.createNotificationChannel(channel)
131 | }
132 | }
133 | }
134 |
135 | }
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/menu/ZoomxMenuService.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.menu;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.graphics.PixelFormat;
7 | import android.os.Binder;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.os.IBinder;
11 | import android.provider.Settings;
12 | import android.support.annotation.Nullable;
13 | import android.view.Gravity;
14 | import android.view.View;
15 | import android.view.WindowManager;
16 |
17 | import com.zoomx.zoomx.ui.notification.ZoomxNotification;
18 |
19 | /**
20 | * Created by Ahmed Fathallah on 12/14/2017.
21 | */
22 |
23 | public class ZoomxMenuService extends Service implements MainActionMenu.ActionMenuEventsListener {
24 |
25 | private static final String MENU_STATE_KEY = "menuActionState";
26 | private static final String SHOW_MENU_KEY = "showMenu";
27 | private static final String HIDE_MENU_KEY = "hideMenu";
28 |
29 | private MainActionMenu menuHeadLayout;
30 | private MenuCloseView menuCloseView;
31 | private WindowManager mWindowManager;
32 | private WindowManager.LayoutParams menuParams;
33 |
34 | public static void showMenuHead(Context context) {
35 | Intent intent = new Intent(context, ZoomxMenuService.class);
36 | Bundle bundle = new Bundle();
37 | bundle.putString(MENU_STATE_KEY, SHOW_MENU_KEY);
38 | intent.putExtras(bundle);
39 | context.startService(intent);
40 | }
41 |
42 | public static void hideMenuHead(Context context) {
43 | Intent intent = new Intent(context, ZoomxMenuService.class);
44 | Bundle bundle = new Bundle();
45 | bundle.putString(MENU_STATE_KEY, HIDE_MENU_KEY);
46 | intent.putExtras(bundle);
47 | context.startService(intent);
48 | }
49 |
50 | @Override
51 | public void onCreate() {
52 | super.onCreate();
53 | menuHeadLayout = new MainActionMenu(this, this);
54 | ZoomxNotification.createNotificationChannel(this);
55 | menuCloseView = new MenuCloseView(this);
56 | menuParams = new WindowManager.LayoutParams(
57 | WindowManager.LayoutParams.WRAP_CONTENT,
58 | WindowManager.LayoutParams.WRAP_CONTENT,
59 | getWindowOverlayFlag(),
60 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
61 | PixelFormat.TRANSLUCENT);
62 |
63 | menuParams.gravity = Gravity.CENTER_VERTICAL | Gravity.END;
64 | menuParams.x = 0;
65 | menuParams.y = 100;
66 |
67 | mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
68 | }
69 |
70 | private int getWindowOverlayFlag() {
71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
72 | return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
73 | else
74 | return WindowManager.LayoutParams.TYPE_PHONE;
75 | }
76 |
77 | @Override
78 | public int onStartCommand(Intent intent, int flags, int startId) {
79 |
80 | if (intent != null && intent.getExtras() != null) {
81 | if (SHOW_MENU_KEY.equals(intent.getExtras().getString(MENU_STATE_KEY))) {
82 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
83 | Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
84 | myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
85 | startActivity(myIntent);
86 | } else {
87 | if (menuHeadLayout.getWindowToken() == null) {
88 | mWindowManager.addView(menuHeadLayout, menuParams);
89 | // addCloseView();
90 | }
91 | }
92 | } else if (HIDE_MENU_KEY.equals(intent.getExtras().getString(MENU_STATE_KEY))) {
93 | hideMenu();
94 | }
95 | }
96 |
97 | return START_NOT_STICKY;
98 | }
99 |
100 | private void hideMenu() {
101 | try {
102 | if (this.menuHeadLayout != null && menuHeadLayout.getWindowToken() != null) {
103 | this.mWindowManager.removeView(this.menuHeadLayout);
104 | this.menuHeadLayout = null;
105 | this.stopSelf();
106 | }
107 | } catch (IllegalArgumentException e) {
108 | e.printStackTrace();
109 | }
110 |
111 | }
112 |
113 | @Override
114 | public void onDestroy() {
115 | super.onDestroy();
116 | if (menuHeadLayout != null) mWindowManager.removeView(menuHeadLayout);
117 | }
118 |
119 | @Nullable
120 | @Override
121 | public IBinder onBind(Intent intent) {
122 | return new LocalBinder();
123 | }
124 |
125 | @Override
126 | public void OnMenuMoved(float dx, float dy) {
127 | menuCloseView.setVisibility(View.VISIBLE);
128 | menuParams.x = (int) (menuParams.x - dx);
129 | menuParams.y = (int) (menuParams.y + dy);
130 | mWindowManager.updateViewLayout(menuHeadLayout, menuParams);
131 | }
132 |
133 | private void addCloseView() {
134 | mWindowManager.addView(menuCloseView, buildLayoutParamsForClose());
135 | }
136 |
137 | private WindowManager.LayoutParams buildLayoutParamsForClose() {
138 | int x = 0;
139 | int y = 0;
140 | WindowManager.LayoutParams params = new WindowManager.LayoutParams(
141 | WindowManager.LayoutParams.WRAP_CONTENT,
142 | WindowManager.LayoutParams.WRAP_CONTENT,
143 | getWindowOverlayFlag(),
144 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
145 | PixelFormat.TRANSPARENT);
146 | params.x = x;
147 | params.y = y;
148 | return params;
149 | }
150 |
151 | /**
152 | * Class used for the client Binder. Because we know this service always
153 | * runs in the same process as its clients, we don't need to deal with IPC.
154 | */
155 | public class LocalBinder extends Binder {
156 | ZoomxMenuService getService() {
157 | // Return this instance of LocalService so clients can call public methods
158 | return ZoomxMenuService.this;
159 | }
160 | }
161 |
162 | }
163 |
164 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/menu/MainActionMenu.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.menu;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Build;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.RequiresApi;
8 | import android.view.MotionEvent;
9 | import android.view.View;
10 | import android.view.ViewConfiguration;
11 | import android.widget.FrameLayout;
12 | import android.widget.ImageView;
13 |
14 | import com.zoomx.zoomx.R;
15 | import com.zoomx.zoomx.ui.info.InfoActivity;
16 | import com.zoomx.zoomx.ui.notification.ScreenShotActivity;
17 | import com.zoomx.zoomx.ui.requestlist.RequestActivity;
18 | import com.zoomx.zoomx.ui.settings.SettingActivity;
19 |
20 | /**
21 | * Created by Ahmed Fathallah on 12/13/2017.
22 | */
23 |
24 | public class MainActionMenu extends FrameLayout implements View.OnClickListener {
25 |
26 | boolean isScrolling = false;
27 | private ImageView menuButton;
28 | private View expandedView;
29 | private boolean isMenuOpened = false;
30 | private ActionMenuEventsListener menuEventsListener;
31 | private float initialTouchX;
32 | private float initialTouchY;
33 | private float startX, startY;
34 | private float prevX, prevY;
35 | private int mTouchSlop;
36 |
37 | public MainActionMenu(@NonNull Context context, ActionMenuEventsListener listener) {
38 | super(context);
39 | initUI(context);
40 | menuEventsListener = listener;
41 | }
42 |
43 | @RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
44 | private void initUI(Context context) {
45 | View view = inflate(context, R.layout.view_main_actions_menu, this);
46 | menuButton = view.findViewById(R.id.menu_main_fab);
47 | ImageView dismissButton = view.findViewById(R.id.menu_dismiss_fab);
48 | ImageView settingsButton = view.findViewById(R.id.menu_settings_fab);
49 | ImageView featuresButton = view.findViewById(R.id.menu_features_fab);
50 | ImageView infoButton = view.findViewById(R.id.menu_info_fab);
51 | ImageView screenShotButton = view.findViewById(R.id.screen_shot_fab);
52 | expandedView = view.findViewById(R.id.menu_expanded_view);
53 |
54 | menuButton.setOnClickListener(this);
55 | dismissButton.setOnClickListener(this);
56 | settingsButton.setOnClickListener(this);
57 | featuresButton.setOnClickListener(this);
58 | infoButton.setOnClickListener(this);
59 | screenShotButton.setOnClickListener(this);
60 | ViewConfiguration mViewConfiguration = ViewConfiguration.get(getContext());
61 | mTouchSlop = mViewConfiguration.getScaledTouchSlop();
62 | }
63 |
64 | @Override
65 | public void onClick(View v) {
66 | int id = v.getId();
67 | if (id == R.id.menu_main_fab) {
68 | expand();
69 | } else if (id == R.id.menu_dismiss_fab) {
70 | collapse();
71 | } else if (id == R.id.menu_settings_fab) {
72 | Intent intent = new Intent(getContext(), SettingActivity.class);
73 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
74 | getContext().startActivity(intent);
75 | } else if (id == R.id.menu_features_fab) {
76 | Intent intent = new Intent(getContext(), RequestActivity.class);
77 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
78 | getContext().startActivity(intent);
79 | } else if (id == R.id.menu_info_fab) {
80 | Intent intent = new Intent(getContext(), InfoActivity.class);
81 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
82 | getContext().startActivity(intent);
83 | }else if (id == R.id.screen_shot_fab){
84 | Intent intent = new Intent(getContext(), ScreenShotActivity.class);
85 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
86 | getContext().startActivity(intent);
87 | collapse();
88 | }
89 | }
90 |
91 | public void collapse() {
92 | expandedView.setVisibility(GONE);
93 | menuButton.setVisibility(VISIBLE);
94 | isMenuOpened = false;
95 | }
96 |
97 | public void expand() {
98 | expandedView.setVisibility(VISIBLE);
99 | menuButton.setVisibility(GONE);
100 | isMenuOpened = true;
101 | }
102 |
103 | public boolean isMenuOpened() {
104 | return isMenuOpened;
105 | }
106 |
107 | @Override
108 | public boolean performClick() {
109 | return super.performClick();
110 | }
111 |
112 | @Override
113 | public boolean onInterceptTouchEvent(MotionEvent event) {
114 | final int action = event.getAction();
115 |
116 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
117 | isScrolling = false;
118 | return false;
119 | }
120 |
121 | switch (action) {
122 | case MotionEvent.ACTION_DOWN:
123 | startX = event.getRawX();
124 | startY = event.getRawY();
125 | prevX = startX;
126 | prevY = startY;
127 | isScrolling = false;
128 | return false;
129 | case MotionEvent.ACTION_MOVE:
130 | if (isScrolling)
131 | return true;
132 |
133 | int Xdiff = (int) (event.getRawX() - startX);
134 | int Ydiff = (int) (event.getRawY() - startY);
135 | if (Math.abs(Ydiff) > mTouchSlop || Math.abs(Xdiff) > mTouchSlop) {
136 | // Start scrolling!
137 | isScrolling = true;
138 | return true;
139 | }
140 | break;
141 | }
142 | return false;
143 | }
144 |
145 | @Override
146 | public boolean onTouchEvent(MotionEvent event) {
147 | float currX = event.getRawX();
148 | float currY = event.getRawY();
149 | switch (event.getAction()) {
150 | // case MotionEvent.ACTION_DOWN:
151 | // startX = currX;
152 | // startY = currY;
153 | // prevX = currX;
154 | // prevY = currY;
155 | // break;
156 | case MotionEvent.ACTION_MOVE:
157 | if (menuEventsListener != null) {
158 | float dX = (currX - prevX);
159 | float dY = (currY - prevY);
160 | prevX = currX;
161 | prevY = currY;
162 | menuEventsListener.OnMenuMoved(dX, dY);
163 | }
164 | break;
165 | }
166 | return true;
167 | }
168 |
169 |
170 | public interface ActionMenuEventsListener {
171 | void OnMenuMoved(float dx, float dy);
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/model/RequestEntity.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.model;
2 |
3 | import android.arch.persistence.room.Entity;
4 | import android.arch.persistence.room.PrimaryKey;
5 | import android.arch.persistence.room.TypeConverters;
6 |
7 | import com.zoomx.zoomx.db.converters.HeaderConverter;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Date;
11 | import java.util.Map;
12 |
13 | /**
14 | * Created by Ahmed Fathallah on 11/20/2017.
15 | */
16 |
17 | @Entity(tableName = "requests")
18 | public class RequestEntity {
19 | @PrimaryKey(autoGenerate = true)
20 | private int id;
21 | private int code;
22 | private String method;
23 | private Date startDate;
24 | private String url;
25 | private String responseBody;
26 | private String requestBody;
27 | private long tookTime;
28 | private long responseSizeInBytes;
29 |
30 | @TypeConverters(HeaderConverter.class)
31 | private HeaderViewModel requestHeaders;
32 | @TypeConverters(HeaderConverter.class)
33 | private HeaderViewModel responseHeaders;
34 |
35 | public RequestEntity() {
36 | }
37 |
38 | private RequestEntity(int code, String method, Date startDate, String url
39 | , String responseBody, String requestBody, long tookTime, long responseSizeInBytes
40 | , HeaderViewModel requestHeaders, HeaderViewModel responseHeaders) {
41 | this.code = code;
42 | this.method = method;
43 | this.startDate = startDate;
44 | this.url = url;
45 | this.responseBody = responseBody;
46 | this.requestBody = requestBody;
47 | this.tookTime = tookTime;
48 | this.responseSizeInBytes = responseSizeInBytes;
49 | this.requestHeaders = requestHeaders;
50 | this.responseHeaders = responseHeaders;
51 | }
52 |
53 | //region Setter and getter
54 | public int getId() {
55 | return id;
56 | }
57 |
58 | public void setId(int id) {
59 | this.id = id;
60 | }
61 |
62 | public int getCode() {
63 | return code;
64 | }
65 |
66 | public void setCode(int code) {
67 | this.code = code;
68 | }
69 |
70 | public String getMethod() {
71 | return method != null ? method : "";
72 | }
73 |
74 | public void setMethod(String method) {
75 | this.method = method;
76 | }
77 |
78 | public Date getStartDate() {
79 | return startDate;
80 | }
81 |
82 | public void setStartDate(Date startDate) {
83 | this.startDate = startDate;
84 | }
85 |
86 | public String getUrl() {
87 | return url != null ? url : "missing";
88 | }
89 |
90 | public void setUrl(String url) {
91 | this.url = url;
92 | }
93 |
94 | public String getResponseBody() {
95 | return responseBody != null ? responseBody : "";
96 | }
97 |
98 | public void setResponseBody(String responseBody) {
99 | this.responseBody = responseBody;
100 | }
101 |
102 | public String getRequestBody() {
103 | return requestBody != null ? requestBody : "";
104 | }
105 |
106 | public void setRequestBody(String requestBody) {
107 | this.requestBody = requestBody;
108 | }
109 |
110 | public long getTookTime() {
111 | return tookTime;
112 | }
113 |
114 | public void setTookTime(long tookTime) {
115 | this.tookTime = tookTime;
116 | }
117 |
118 | public long getResponseSizeInBytes() {
119 | return responseSizeInBytes;
120 | }
121 |
122 | public void setResponseSizeInBytes(long responseSizeInBytes) {
123 | this.responseSizeInBytes = responseSizeInBytes;
124 | }
125 |
126 | public HeaderViewModel getRequestHeaders() {
127 | return requestHeaders;
128 | }
129 |
130 | public void setRequestHeaders(HeaderViewModel requestHeaders) {
131 | this.requestHeaders = requestHeaders;
132 | }
133 |
134 | public HeaderViewModel getResponseHeaders() {
135 | return responseHeaders;
136 | }
137 |
138 | public void setResponseHeaders(HeaderViewModel responseHeaders) {
139 | this.responseHeaders = responseHeaders;
140 | }
141 |
142 | @Override
143 | public String toString() {
144 | return id + " " + code + " " + method + " "
145 | + startDate + " " + url + " " + requestBody + " " + responseBody + " " + tookTime;
146 | }
147 | //endregion
148 |
149 | //region builder
150 | public static class Builder {
151 |
152 | private int code;
153 | private String method;
154 | private Date startDate;
155 | private String url;
156 | private String responseBody;
157 | private String requestBody;
158 | private long tookTime;
159 | private long responseSizeInBytes;
160 | private ArrayList requestHeaders;
161 | private ArrayList responseHeaders;
162 |
163 | public Builder setCode(int code) {
164 | this.code = code;
165 | return this;
166 | }
167 |
168 | public Builder setMethod(String method) {
169 | this.method = method;
170 | return this;
171 | }
172 |
173 | public Builder setStartDate(Date startDate) {
174 | this.startDate = startDate;
175 | return this;
176 | }
177 |
178 | public Builder setUrl(String url) {
179 | this.url = url;
180 | return this;
181 | }
182 |
183 | public Builder setResponseBody(String responseBody) {
184 | this.responseBody = responseBody;
185 | return this;
186 | }
187 |
188 | public Builder setRequestBody(String requestBody) {
189 | this.requestBody = requestBody;
190 | return this;
191 | }
192 |
193 | public Builder setTookTime(long tookTime) {
194 | this.tookTime = tookTime;
195 | return this;
196 | }
197 |
198 | public Builder setResponseSizeInBytes(long responseSizeInBytes) {
199 | this.responseSizeInBytes = responseSizeInBytes;
200 | return this;
201 | }
202 |
203 | public Builder setRequestHeaders(ArrayList requestHeaders) {
204 | this.requestHeaders = requestHeaders;
205 | return this;
206 | }
207 |
208 | public Builder setResponseHeaders(ArrayList responseHeaders) {
209 | this.responseHeaders = responseHeaders;
210 | return this;
211 | }
212 |
213 | public RequestEntity create() {
214 | HeaderViewModel requestHeadersModel = new HeaderViewModel(requestHeaders);
215 | HeaderViewModel responseHeadersModel = new HeaderViewModel(responseHeaders);
216 |
217 | return new RequestEntity(code, method, startDate, url, responseBody, requestBody, tookTime
218 | , responseSizeInBytes, requestHeadersModel, responseHeadersModel);
219 | }
220 | }
221 | //endregion
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/zoomx/src/main/res/layout/request_details_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
25 |
26 |
31 |
32 |
39 |
40 |
41 |
46 |
47 |
52 |
53 |
58 |
59 |
66 |
67 |
68 |
69 |
74 |
75 |
80 |
81 |
86 |
87 |
94 |
95 |
96 |
97 |
102 |
103 |
108 |
109 |
114 |
115 |
122 |
123 |
124 |
125 |
130 |
131 |
138 |
139 |
143 |
144 |
145 |
153 |
154 |
155 |
156 |
161 |
162 |
169 |
170 |
174 |
175 |
183 |
184 |
185 |
186 |
191 |
192 |
200 |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/requestdetails/RequestDetailsFragment.java:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.requestdetails;
2 |
3 | import android.arch.lifecycle.Observer;
4 | import android.arch.lifecycle.ViewModelProviders;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.content.pm.ResolveInfo;
8 | import android.net.Uri;
9 | import android.os.AsyncTask;
10 | import android.os.Bundle;
11 | import android.support.annotation.Nullable;
12 | import android.support.v4.app.Fragment;
13 | import android.support.v4.app.ShareCompat;
14 | import android.support.v4.content.FileProvider;
15 | import android.support.v7.widget.DefaultItemAnimator;
16 | import android.support.v7.widget.LinearLayoutManager;
17 | import android.support.v7.widget.RecyclerView;
18 | import android.view.LayoutInflater;
19 | import android.view.Menu;
20 | import android.view.MenuInflater;
21 | import android.view.MenuItem;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.widget.RelativeLayout;
25 | import android.widget.TextView;
26 |
27 | import com.zoomx.zoomx.R;
28 | import com.zoomx.zoomx.model.RequestEntity;
29 | import com.zoomx.zoomx.ui.requestlist.RequestListFragment;
30 | import com.zoomx.zoomx.util.ColorUtils;
31 | import com.zoomx.zoomx.util.FormatUtil;
32 |
33 | import java.io.File;
34 | import java.io.FileWriter;
35 | import java.io.IOException;
36 | import java.util.ArrayList;
37 | import java.util.HashMap;
38 | import java.util.List;
39 | import java.util.Map;
40 |
41 | /**
42 | * Created by Ahmed Fathallah on 2/17/2018.
43 | */
44 |
45 | public class RequestDetailsFragment extends Fragment {
46 |
47 | public static final String BODY_URL_KEY = "url";
48 | public static final String BODY_JSON_KEY = "body";
49 | private static final String TEXT_DATA_TYPE = "text/plain";
50 | private RequestDetailsViewModel viewModel;
51 | private RequestEntity mRequestEntity;
52 | private View view;
53 | private RelativeLayout requestLayout, responseLayout;
54 |
55 |
56 | public static RequestDetailsFragment newInstance(int requestId) {
57 | Bundle args = new Bundle();
58 | args.putInt(RequestListFragment.REQUEST_ID, requestId);
59 | RequestDetailsFragment fragment = new RequestDetailsFragment();
60 | fragment.setArguments(args);
61 | return fragment;
62 | }
63 |
64 |
65 | @Nullable
66 | @Override
67 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
68 | view = inflater.inflate(R.layout.request_details_fragment, container, false);
69 | ((RequestDetailsActivity) getActivity()).getSupportActionBar().setTitle(R.string.request_details_screen_title);
70 | setHasOptionsMenu(true);
71 | if (getArguments() != null && getArguments().containsKey(RequestListFragment.REQUEST_ID)) {
72 | int requestId = (int) getArguments().get(RequestListFragment.REQUEST_ID);
73 | viewModel = ViewModelProviders.of(this).get(RequestDetailsViewModel.class);
74 | viewModel.getRequestById(requestId).observe(this, new Observer() {
75 | @Override
76 | public void onChanged(@Nullable RequestEntity requestEntity) {
77 | Map item = new HashMap<>();
78 | if (requestEntity != null) {
79 | item.put(RequestListFragment.REQUEST_ID, requestEntity.getMethod());
80 | initUi(requestEntity);
81 | }
82 | }
83 | });
84 | }
85 | return view;
86 | }
87 |
88 | private void initUi(final RequestEntity requestEntity) {
89 | mRequestEntity = requestEntity;
90 | TextView methodTextView = view.findViewById(R.id.request_details_method_txt);
91 | TextView codeTextView = view.findViewById(R.id.request_details_code_txt);
92 | TextView urlTextView = view.findViewById(R.id.request_details_url_txt);
93 | TextView dateTextView = view.findViewById(R.id.request_details_startDate_txt);
94 | responseLayout = view.findViewById(R.id.request_details_response_layout);
95 | requestLayout = view.findViewById(R.id.request_details_request_body_layout);
96 | RecyclerView recyclerView = view.findViewById(R.id.request_details_recycler_view);
97 | RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getContext());
98 | recyclerView.setLayoutManager(mLayoutManager);
99 | recyclerView.setNestedScrollingEnabled(false);
100 | recyclerView.setItemAnimator(new DefaultItemAnimator());
101 | RequestDetailsAdapter requestDetailsAdapter = new RequestDetailsAdapter(requestEntity.getRequestHeaders().getHeadersMap(), requestEntity.getResponseHeaders().getHeadersMap());
102 |
103 | recyclerView.setAdapter(requestDetailsAdapter);
104 | methodTextView.setText(requestEntity.getMethod());
105 | codeTextView.setText(String.valueOf(requestEntity.getCode()));
106 | codeTextView.setTextColor(ColorUtils.getCodeColor(requestEntity.getCode(), getContext()));
107 | urlTextView.setText(requestEntity.getUrl());
108 | dateTextView.setText(FormatUtil.formatDate(requestEntity.getStartDate(), FormatUtil.DATE_FORMAT));
109 |
110 | responseLayout.setOnClickListener(new View.OnClickListener() {
111 | @Override
112 | public void onClick(View v) {
113 | showBody(requestEntity.getUrl(), requestEntity.getResponseBody());
114 | }
115 | });
116 |
117 | requestLayout.setOnClickListener(new View.OnClickListener() {
118 | @Override
119 | public void onClick(View v) {
120 | showBody(requestEntity.getUrl(), requestEntity.getRequestBody());
121 | }
122 | });
123 | }
124 |
125 | public void showBody(String url, String body) {
126 | Intent intent = new Intent(getActivity(), JsonViewActivity.class);
127 | intent.putExtra(BODY_URL_KEY, url);
128 | intent.putExtra(BODY_JSON_KEY, body);
129 | startActivity(intent);
130 | }
131 |
132 | @Override
133 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
134 | inflater.inflate(R.menu.menu_requests_details, menu);
135 | }
136 |
137 | @Override
138 | public boolean onOptionsItemSelected(MenuItem item) {
139 | if (item.getItemId() == R.id.action_share_request) {
140 | shareRequest();
141 | return true;
142 | }
143 | return super.onOptionsItemSelected(item);
144 | }
145 |
146 | private void shareRequest() {
147 | new RequestToFileTask().execute(mRequestEntity);
148 | }
149 |
150 |
151 | public class RequestToFileTask extends AsyncTask {
152 | @Override
153 | protected File doInBackground(RequestEntity... requestEntities) {
154 | try {
155 | RequestEntity requestEntity = requestEntities[0];
156 | File tmpFile = File.createTempFile(getFileName(), ".txt", getActivity().getCacheDir());
157 | FileWriter writer = new FileWriter(tmpFile);
158 | writer.write(requestEntity.getUrl());
159 | writer.append("\n");
160 | writer.append(requestEntity.getRequestBody());
161 | writer.append("\n");
162 | writer.append(requestEntity.getResponseBody());
163 | writer.close();
164 | return tmpFile;
165 | } catch (IOException e) {
166 | e.printStackTrace();
167 | return null;
168 | }
169 | }
170 |
171 |
172 | @Override
173 | protected void onPostExecute(File file) {
174 | if (file != null) {
175 | Uri sharedFileUri = FileProvider.getUriForFile(getActivity(), "com.zoomx.fileprovider", file);
176 | Intent share = ShareCompat.IntentBuilder.from(getActivity())
177 | .setType(TEXT_DATA_TYPE)
178 | .setStream(sharedFileUri)
179 | .setChooserTitle(R.string.share_file_chooser_title)
180 | .createChooserIntent()
181 | .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
182 | startActivity(share);
183 |
184 | }
185 | }
186 |
187 | private String getFileName() {
188 | return Uri.parse(mRequestEntity.getUrl()).getLastPathSegment();
189 | }
190 |
191 | private void grandReadPermissions(Intent intent, Uri sharedFileUri) {
192 | final PackageManager packageManager = getActivity().getPackageManager();
193 | final List activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
194 | for (ResolveInfo resolvedIntentInfo : activities) {
195 | final String packageName = resolvedIntentInfo.activityInfo.packageName;
196 | getActivity().grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
197 | }
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/zoomx/src/main/java/com/zoomx/zoomx/ui/notification/ScreenShotActivity.kt:
--------------------------------------------------------------------------------
1 | package com.zoomx.zoomx.ui.notification
2 |
3 | import android.app.Activity
4 | import android.support.v7.app.AppCompatActivity
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.graphics.Bitmap
8 | import android.graphics.PixelFormat
9 | import android.graphics.Point
10 | import android.hardware.display.DisplayManager
11 | import android.hardware.display.VirtualDisplay
12 | import android.media.Image
13 | import android.media.ImageReader
14 | import android.media.projection.MediaProjection
15 | import android.media.projection.MediaProjectionManager
16 | import android.os.*
17 | import android.support.annotation.Nullable
18 | import android.support.annotation.RequiresApi
19 | import android.util.DisplayMetrics
20 | import android.util.Log
21 | import android.view.Display
22 | import android.view.OrientationEventListener
23 | import java.io.File
24 | import java.io.FileOutputStream
25 | import java.io.IOException
26 | import java.util.*
27 |
28 | class ScreenShotActivity : AppCompatActivity() {
29 |
30 | object Constants {
31 | const val SCREEN_SHOT_PATH = "file_path"
32 | }
33 |
34 |
35 | private val TAG = ScreenShotActivity::class.java.name
36 | private val REQUEST_CODE = 100
37 | private var STORE_DIRECTORY: String? = null
38 | private var IMAGES_PRODUCED: Int = 0
39 | private val SCREENCAP_NAME = "screencap"
40 | private val VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
41 | private var sMediaProjection: MediaProjection? = null
42 |
43 | private var mProjectionManager: MediaProjectionManager? = null
44 | private var mImageReader: ImageReader? = null
45 | private var mHandler: Handler? = null
46 | private var mDisplay: Display? = null
47 | private var mVirtualDisplay: VirtualDisplay? = null
48 | private var mDensity: Int = 0
49 | private var mWidth: Int = 0
50 | private var mHeight: Int = 0
51 | private var mRotation: Int = 0
52 | private var mOrientationChangeCallback: OrientationChangeCallback? = null
53 | var consumed = false
54 | var file: File? = null
55 |
56 | @RequiresApi(Build.VERSION_CODES.KITKAT)
57 | private inner class ImageAvailableListener : ImageReader.OnImageAvailableListener {
58 | @RequiresApi(Build.VERSION_CODES.KITKAT)
59 | override fun onImageAvailable(reader: ImageReader) {
60 | if (!consumed) {
61 | consumed = true
62 | var image: Image? = null
63 | var fos: FileOutputStream? = null
64 | var bitmap: Bitmap? = null
65 |
66 | try {
67 | image = reader.acquireLatestImage()
68 | if (image != null) {
69 | val planes = image.planes
70 | val buffer = planes[0].buffer
71 | val pixelStride = planes[0].pixelStride
72 | val rowStride = planes[0].rowStride
73 | val rowPadding = rowStride - pixelStride * mWidth
74 | // create bitmap
75 | bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888)
76 | bitmap!!.copyPixelsFromBuffer(buffer)
77 | // write bitmap to a file
78 | file = File("$STORE_DIRECTORY/myscreen_$IMAGES_PRODUCED ${Date().time}.png")
79 | fos = FileOutputStream(file)
80 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
81 | IMAGES_PRODUCED++
82 | Log.e(TAG, "captured image: $IMAGES_PRODUCED")
83 | stopProjection()
84 |
85 | var intent = Intent(this@ScreenShotActivity, ScreenshotViewerActivity::class.java)
86 | intent.putExtra(Constants.SCREEN_SHOT_PATH, file?.path)
87 | startActivity(intent)
88 |
89 | }
90 | } catch (e: Exception) {
91 | e.printStackTrace()
92 | } finally {
93 | if (fos != null) {
94 | try {
95 | fos.close()
96 | } catch (ioe: IOException) {
97 | ioe.printStackTrace()
98 | }
99 |
100 | }
101 | bitmap?.recycle()
102 | image?.close()
103 | }
104 | }
105 | }
106 | }
107 |
108 |
109 | private inner class OrientationChangeCallback internal constructor(context: Context) : OrientationEventListener(context) {
110 |
111 | override fun onOrientationChanged(orientation: Int) {
112 | val rotation = mDisplay!!.rotation
113 | if (rotation != mRotation) {
114 | mRotation = rotation
115 | try {
116 | // clean up
117 | if (mVirtualDisplay != null) mVirtualDisplay!!.release()
118 | if (mImageReader != null) mImageReader!!.setOnImageAvailableListener(null, null)
119 |
120 | // re-create virtual display depending on device width / height
121 | createVirtualDisplay()
122 | } catch (e: Exception) {
123 | e.printStackTrace()
124 | }
125 |
126 | }
127 | }
128 | }
129 |
130 |
131 | override fun onResume() {
132 | try {
133 | Thread.sleep(5000)
134 | } catch (e: InterruptedException) {
135 | e.printStackTrace()
136 | }
137 |
138 | finish()
139 | super.onResume()
140 | }
141 |
142 | /****************************************** Activity Lifecycle methods */
143 |
144 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
145 | override fun onCreate(savedInstanceState: Bundle?) {
146 | super.onCreate(savedInstanceState)
147 | // setContentView(R.layout.activity_main);
148 |
149 | // call for the projection manager
150 | // display metrics
151 | val metrics = resources.displayMetrics
152 | mDensity = metrics.densityDpi
153 | mDisplay = windowManager.defaultDisplay
154 | mProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
155 | startProjection()
156 | // start capture handling thread
157 | object : Thread() {
158 | override fun run() {
159 | Looper.prepare()
160 | mHandler = Handler()
161 | Looper.loop()
162 | }
163 | }.start()
164 | }
165 |
166 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
167 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
168 | super.onActivityResult(requestCode, resultCode, data)
169 | if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
170 | sMediaProjection = mProjectionManager!!.getMediaProjection(resultCode, data)
171 | val externalFilesDir = getExternalFilesDir(null)
172 | // val externalFilesDir = Environment.getExternalStorageDirectory();
173 |
174 | if (externalFilesDir != null) {
175 | STORE_DIRECTORY = externalFilesDir.absolutePath + "/screenshots/"
176 | val storeDirectory = File(STORE_DIRECTORY!!)
177 | if (!storeDirectory.exists()) {
178 | val success = storeDirectory.mkdirs()
179 | if (!success) {
180 | return
181 | }
182 | }
183 | } else {
184 | return
185 | }
186 |
187 | // create virtual display depending on device width / height
188 | createVirtualDisplay()
189 | // register orientation change callback
190 | mOrientationChangeCallback = OrientationChangeCallback(this)
191 | if (mOrientationChangeCallback!!.canDetectOrientation()) {
192 | mOrientationChangeCallback!!.enable()
193 | }
194 | }
195 |
196 | }
197 |
198 | /****************************************** UI Widget Callbacks */
199 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
200 | private fun startProjection() {
201 | startActivityForResult(mProjectionManager!!.createScreenCaptureIntent(), REQUEST_CODE)
202 | }
203 |
204 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
205 | private fun stopProjection() {
206 | mHandler!!.post {
207 | if (sMediaProjection != null) {
208 | sMediaProjection!!.stop()
209 | mVirtualDisplay?.release()
210 | }
211 | }
212 | }
213 |
214 | /****************************************** Factoring Virtual Display creation */
215 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
216 | private fun createVirtualDisplay() {
217 | // get width and height
218 | val size = Point()
219 | mDisplay!!.getSize(size)
220 | mWidth = size.x
221 | mHeight = size.y
222 | // start capture reader
223 | mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 1)
224 | mVirtualDisplay = sMediaProjection!!.createVirtualDisplay(SCREENCAP_NAME, mWidth, mHeight, mDensity, VIRTUAL_DISPLAY_FLAGS, mImageReader!!.surface, null, mHandler)
225 | mImageReader!!.setOnImageAvailableListener(ImageAvailableListener(), mHandler)
226 |
227 | }
228 | }
229 |
--------------------------------------------------------------------------------