From f58a739dd5f0fe38787f5cb23465801826a8fb8a Mon Sep 17 00:00:00 2001 From: sergi Date: Tue, 13 Apr 2021 19:18:31 +0200 Subject: [PATCH] First release commit --- CONTRIBUTING.md | 1 + Logo_Pointer.png | Bin 0 -> 10277 bytes README.md | 73 +- build.gradle | 32 + compile.sh | 9 + connectivity.png | Bin 0 -> 83570 bytes demo-filesharing/.gitignore | 1 + demo-filesharing/app.iml | 232 ++++ demo-filesharing/build.gradle | 101 ++ demo-filesharing/proguard-rules.pro | 23 + .../localsharing/ExampleInstrumentedTest.java | 26 + demo-filesharing/src/main/AndroidManifest.xml | 111 ++ .../network/datahop/localsharing/App.java | 40 + .../datahop/localsharing/MainActivity.java | 1165 +++++++++++++++++ .../datahop/localsharing/data/AddFile.java | 201 +++ .../data/ContentDatabaseHandler.java | 699 ++++++++++ .../datahop/localsharing/data/User.java | 60 + .../localsharing/ui/GroupActivity.java | 546 ++++++++ .../datahop/localsharing/ui/UserActivity.java | 209 +++ .../ui/fragments/FragmentCallback.java | 6 + .../ui/fragments/GroupsListFragment.java | 146 +++ .../ui/fragments/ServiceFragment.java | 187 +++ .../ui/fragments/SettingsFragment.java | 188 +++ .../detail/AbstractRecyclerAdapter.java | 173 +++ .../detail/CompletingViewHolder.java | 51 + .../ui/fragments/detail/ContentAdapter.java | 194 +++ .../ui/fragments/detail/DataViewHolder.java | 198 +++ .../fragments/detail/GroupsListAdapter.java | 167 +++ .../ui/fragments/detail/ItemDecoration.java | 59 + .../detail/OnGroupClickListener.java | 13 + .../ui/fragments/detail/OnTaskCompleted.java | 5 + .../fragments/detail/OnUserClickListener.java | 12 + .../ui/fragments/detail/UsersListAdapter.java | 144 ++ .../ui/splash/SplashActivity.java | 76 ++ .../datahop/localsharing/utils/Config.java | 69 + .../network/datahop/localsharing/utils/G.java | 102 ++ .../datahop/localsharing/utils/PathUtil.java | 93 ++ .../utils/SettingsPreferences.java | 231 ++++ .../datahop/localsharing/utils/Util.java | 146 +++ .../main/res/drawable-v24/dummy_thumbnail.png | Bin 0 -> 5164 bytes .../res/drawable-v24/dummy_thumbnail2.png | Bin 0 -> 2654 bytes .../src/main/res/drawable-v24/ic_datahop.xml | 13 + .../src/main/res/drawable-v24/ic_excel.png | Bin 0 -> 8602 bytes .../src/main/res/drawable-v24/ic_icon.xml | 6 + .../main/res/drawable-v24/ic_icon_white.xml | 13 + .../drawable-v24/ic_launcher_foreground.xml | 34 + .../src/main/res/drawable-v24/ic_pdf.png | Bin 0 -> 13649 bytes .../main/res/drawable-v24/ic_powerpoint.png | Bin 0 -> 8661 bytes .../src/main/res/drawable-v24/ic_unknown.png | Bin 0 -> 3420 bytes .../src/main/res/drawable-v24/ic_word.png | Bin 0 -> 4269 bytes .../src/main/res/drawable/circle.xml | 7 + .../decorator_activity_my_groups_list.xml | 6 + .../src/main/res/drawable/ic_account.xml | 9 + .../res/drawable/ic_launcher_background.xml | 170 +++ .../src/main/res/drawable/ic_menu_camera.xml | 12 + .../src/main/res/drawable/ic_menu_gallery.xml | 9 + .../src/main/res/drawable/ic_menu_manage.xml | 9 + .../src/main/res/drawable/ic_menu_send.xml | 9 + .../src/main/res/drawable/ic_menu_share.xml | 9 + .../main/res/drawable/ic_menu_slideshow.xml | 9 + .../src/main/res/drawable/side_nav_bar.xml | 9 + .../src/main/res/layout/activity_main.xml | 27 + .../main/res/layout/activity_main_legacy.xml | 27 + .../src/main/res/layout/app_bar_main.xml | 43 + .../src/main/res/layout/circletext.xml | 20 + .../src/main/res/layout/content_main.xml | 20 + .../res/layout/fragment_chat_groups_list.xml | 22 + .../src/main/res/layout/fragment_content.xml | 61 + .../src/main/res/layout/fragment_login.xml | 40 + .../src/main/res/layout/fragment_service.xml | 117 ++ .../src/main/res/layout/fragment_settings.xml | 119 ++ .../main/res/layout/list_item_progress.xml | 69 + .../src/main/res/layout/list_stream_item.xml | 120 ++ .../src/main/res/layout/nav_header_main.xml | 94 ++ .../res/layout/nav_header_main_legacy.xml | 37 + .../src/main/res/layout/row_group.xml | 60 + .../src/main/res/layout/splash_activity.xml | 11 + .../res/layout/splash_activity_legacy.xml | 5 + .../main/res/menu/activity_main_drawer.xml | 35 + demo-filesharing/src/main/res/menu/group.xml | 10 + demo-filesharing/src/main/res/menu/main.xml | 19 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2979 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5114 bytes .../main/res/mipmap-hdpi/ic_stat_looks.png | Bin 0 -> 663 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1543 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2561 bytes .../main/res/mipmap-mdpi/ic_stat_looks.png | Bin 0 -> 466 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4118 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7197 bytes .../main/res/mipmap-xhdpi/ic_stat_looks.png | Bin 0 -> 1066 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7823 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 14043 bytes .../main/res/mipmap-xxhdpi/ic_stat_looks.png | Bin 0 -> 1339 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 11640 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 19864 bytes .../main/res/mipmap-xxxhdpi/ic_stat_looks.png | Bin 0 -> 2079 bytes .../src/main/res/values-v21/color_palette.xml | 306 +++++ .../src/main/res/values-v21/styles.xml | 8 + .../src/main/res/values/colors.xml | 13 + .../src/main/res/values/dimens.xml | 63 + .../src/main/res/values/strings.xml | 81 ++ .../src/main/res/values/styles.xml | 91 ++ .../src/main/res/xml/provider_paths.xml | 4 + .../datahop/localsharing/ExampleUnitTest.java | 17 + eu.png | Bin 0 -> 19827 bytes gradle.properties | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++ gradlew.bat | 84 ++ local.properties | 9 + localfirst/.DS_Store | Bin 0 -> 6148 bytes localfirst/.gitignore | 1 + localfirst/build.gradle | 66 + localfirst/consumer-rules.pro | 0 localfirst/localfirst.iml | 230 ++++ localfirst/proguard-rules.pro | 21 + .../localfirst/ExampleInstrumentedTest.java | 27 + localfirst/src/main/AndroidManifest.xml | 46 + .../localfirst/LocalFirstListener.java | 17 + .../datahop/localfirst/LocalFirstSDK.java | 223 ++++ .../localfirst/backend/DataHopBackend.java | 46 + .../localfirst/backend/DownloadFile.java | 167 +++ .../datahop/localfirst/backend/Log.java | 25 + .../backend/SimpleLoggingBackend.java | 131 ++ .../datahop/localfirst/data/Chunking.java | 147 +++ .../datahop/localfirst/data/Content.java | 144 ++ .../localfirst/data/ContentAdvertisement.java | 170 +++ .../data/ContentDatabaseHandler.java | 696 ++++++++++ .../localfirst/data/ContentDelivery.java | 317 +++++ .../localfirst/data/DataSharingClient.java | 337 +++++ .../localfirst/data/DataSharingServer.java | 92 ++ .../datahop/localfirst/data/Group.java | 145 ++ .../datahop/localfirst/data/HttpServer.java | 179 +++ .../localfirst/data/NewContentEvent.java | 13 + .../datahop/localfirst/data/NewDataEvent.java | 13 + .../datahop/localfirst/data/NewUserEvent.java | 13 + .../datahop/localfirst/data/SaveFile.java | 99 ++ .../localfirst/data/StreamVolleyRequest.java | 56 + .../localfirst/data/UploadFileRequest.java | 82 ++ .../localfirst/net/DataHopService.java | 660 ++++++++++ .../localfirst/net/DiscoveryListener.java | 14 + .../datahop/localfirst/net/LinkListener.java | 20 + .../datahop/localfirst/net/StatsHandler.java | 467 +++++++ .../net/ble/BLEServiceDiscovery.java | 591 +++++++++ .../localfirst/net/ble/BluetoothUtils.java | 220 ++++ .../datahop/localfirst/net/ble/Constants.java | 30 + .../net/ble/GattServerCallback.java | 273 ++++ .../localfirst/net/ble/StringUtils.java | 32 + .../localfirst/net/wifi/HotspotListener.java | 16 + .../net/wifi/WifiDirectHotSpot.java | 309 +++++ .../datahop/localfirst/net/wifi/WifiLink.java | 395 ++++++ .../localfirst/net/wifi/WifiLinkListener.java | 21 + .../datahop/localfirst/utils/Config.java | 69 + .../network/datahop/localfirst/utils/G.java | 92 ++ .../datahop/localfirst/utils/Util.java | 154 +++ localfirst/src/main/res/drawable/ic_icon.xml | 6 + localfirst/src/main/res/values/strings.xml | 3 + .../datahop/localfirst/ExampleUnitTest.java | 17 + settings.gradle | 1 + 160 files changed, 14832 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md create mode 100644 Logo_Pointer.png create mode 100644 build.gradle create mode 100755 compile.sh create mode 100644 connectivity.png create mode 100644 demo-filesharing/.gitignore create mode 100644 demo-filesharing/app.iml create mode 100644 demo-filesharing/build.gradle create mode 100644 demo-filesharing/proguard-rules.pro create mode 100644 demo-filesharing/src/androidTest/java/network/datahop/localsharing/ExampleInstrumentedTest.java create mode 100644 demo-filesharing/src/main/AndroidManifest.xml create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/App.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/MainActivity.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/data/AddFile.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/data/ContentDatabaseHandler.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/data/User.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/GroupActivity.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/UserActivity.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/FragmentCallback.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/GroupsListFragment.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/ServiceFragment.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/SettingsFragment.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/AbstractRecyclerAdapter.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/CompletingViewHolder.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ContentAdapter.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/DataViewHolder.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/GroupsListAdapter.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ItemDecoration.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnGroupClickListener.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnTaskCompleted.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnUserClickListener.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/UsersListAdapter.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/ui/splash/SplashActivity.java create mode 100755 demo-filesharing/src/main/java/network/datahop/localsharing/utils/Config.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/utils/G.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/utils/PathUtil.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/utils/SettingsPreferences.java create mode 100644 demo-filesharing/src/main/java/network/datahop/localsharing/utils/Util.java create mode 100644 demo-filesharing/src/main/res/drawable-v24/dummy_thumbnail.png create mode 100644 demo-filesharing/src/main/res/drawable-v24/dummy_thumbnail2.png create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_datahop.xml create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_excel.png create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_icon.xml create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_icon_white.xml create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_pdf.png create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_powerpoint.png create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_unknown.png create mode 100644 demo-filesharing/src/main/res/drawable-v24/ic_word.png create mode 100644 demo-filesharing/src/main/res/drawable/circle.xml create mode 100644 demo-filesharing/src/main/res/drawable/decorator_activity_my_groups_list.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_account.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_launcher_background.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_menu_camera.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_menu_gallery.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_menu_manage.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_menu_send.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_menu_share.xml create mode 100644 demo-filesharing/src/main/res/drawable/ic_menu_slideshow.xml create mode 100644 demo-filesharing/src/main/res/drawable/side_nav_bar.xml create mode 100644 demo-filesharing/src/main/res/layout/activity_main.xml create mode 100644 demo-filesharing/src/main/res/layout/activity_main_legacy.xml create mode 100644 demo-filesharing/src/main/res/layout/app_bar_main.xml create mode 100644 demo-filesharing/src/main/res/layout/circletext.xml create mode 100644 demo-filesharing/src/main/res/layout/content_main.xml create mode 100644 demo-filesharing/src/main/res/layout/fragment_chat_groups_list.xml create mode 100644 demo-filesharing/src/main/res/layout/fragment_content.xml create mode 100644 demo-filesharing/src/main/res/layout/fragment_login.xml create mode 100644 demo-filesharing/src/main/res/layout/fragment_service.xml create mode 100644 demo-filesharing/src/main/res/layout/fragment_settings.xml create mode 100644 demo-filesharing/src/main/res/layout/list_item_progress.xml create mode 100644 demo-filesharing/src/main/res/layout/list_stream_item.xml create mode 100644 demo-filesharing/src/main/res/layout/nav_header_main.xml create mode 100644 demo-filesharing/src/main/res/layout/nav_header_main_legacy.xml create mode 100644 demo-filesharing/src/main/res/layout/row_group.xml create mode 100644 demo-filesharing/src/main/res/layout/splash_activity.xml create mode 100644 demo-filesharing/src/main/res/layout/splash_activity_legacy.xml create mode 100644 demo-filesharing/src/main/res/menu/activity_main_drawer.xml create mode 100644 demo-filesharing/src/main/res/menu/group.xml create mode 100644 demo-filesharing/src/main/res/menu/main.xml create mode 100644 demo-filesharing/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 demo-filesharing/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100755 demo-filesharing/src/main/res/mipmap-hdpi/ic_stat_looks.png create mode 100644 demo-filesharing/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 demo-filesharing/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100755 demo-filesharing/src/main/res/mipmap-mdpi/ic_stat_looks.png create mode 100644 demo-filesharing/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 demo-filesharing/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100755 demo-filesharing/src/main/res/mipmap-xhdpi/ic_stat_looks.png create mode 100644 demo-filesharing/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 demo-filesharing/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100755 demo-filesharing/src/main/res/mipmap-xxhdpi/ic_stat_looks.png create mode 100644 demo-filesharing/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 demo-filesharing/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100755 demo-filesharing/src/main/res/mipmap-xxxhdpi/ic_stat_looks.png create mode 100644 demo-filesharing/src/main/res/values-v21/color_palette.xml create mode 100644 demo-filesharing/src/main/res/values-v21/styles.xml create mode 100644 demo-filesharing/src/main/res/values/colors.xml create mode 100644 demo-filesharing/src/main/res/values/dimens.xml create mode 100644 demo-filesharing/src/main/res/values/strings.xml create mode 100644 demo-filesharing/src/main/res/values/styles.xml create mode 100644 demo-filesharing/src/main/res/xml/provider_paths.xml create mode 100644 demo-filesharing/src/test/java/network/datahop/localsharing/ExampleUnitTest.java create mode 100644 eu.png create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 local.properties create mode 100644 localfirst/.DS_Store create mode 100644 localfirst/.gitignore create mode 100644 localfirst/build.gradle create mode 100644 localfirst/consumer-rules.pro create mode 100644 localfirst/localfirst.iml create mode 100644 localfirst/proguard-rules.pro create mode 100644 localfirst/src/androidTest/java/network/datahop/localfirst/ExampleInstrumentedTest.java create mode 100644 localfirst/src/main/AndroidManifest.xml create mode 100755 localfirst/src/main/java/network/datahop/localfirst/LocalFirstListener.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/LocalFirstSDK.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/backend/DataHopBackend.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/backend/DownloadFile.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/backend/Log.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/backend/SimpleLoggingBackend.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/Chunking.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/Content.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/ContentAdvertisement.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/ContentDatabaseHandler.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/ContentDelivery.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/DataSharingClient.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/DataSharingServer.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/Group.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/HttpServer.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/NewContentEvent.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/NewDataEvent.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/NewUserEvent.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/SaveFile.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/StreamVolleyRequest.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/data/UploadFileRequest.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/DataHopService.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/DiscoveryListener.java create mode 100755 localfirst/src/main/java/network/datahop/localfirst/net/LinkListener.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/StatsHandler.java create mode 100755 localfirst/src/main/java/network/datahop/localfirst/net/ble/BLEServiceDiscovery.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/ble/BluetoothUtils.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/ble/Constants.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/ble/GattServerCallback.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/ble/StringUtils.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/wifi/HotspotListener.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/wifi/WifiDirectHotSpot.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/net/wifi/WifiLink.java create mode 100755 localfirst/src/main/java/network/datahop/localfirst/net/wifi/WifiLinkListener.java create mode 100755 localfirst/src/main/java/network/datahop/localfirst/utils/Config.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/utils/G.java create mode 100644 localfirst/src/main/java/network/datahop/localfirst/utils/Util.java create mode 100644 localfirst/src/main/res/drawable/ic_icon.xml create mode 100644 localfirst/src/main/res/values/strings.xml create mode 100644 localfirst/src/test/java/network/datahop/localfirst/ExampleUnitTest.java create mode 100644 settings.gradle diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..245759f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +# Contributing Guidelines diff --git a/Logo_Pointer.png b/Logo_Pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..cbaecc94c0f3596d8db020becb3f2114c5343571 GIT binary patch literal 10277 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 MGuJKAzWeh306DOEconnectivity

+ +## Objectives + +* [x] User devices must connect between them without infrastructure participation and without interrupting connectivity to the main infrastructure (e.g., WiFi access points or the +cell network). +* [x] User devices must discover services/applications +and content (available in nearby devices) before actually connecting to those devices. This will save both time and energy from the +process. +* [x] Connectivity should be transparent (and run as +a background process) to the user (i.e., it should not require the +users’ manual intervention. +* [x] Connectivity must take into account power consumption and must implement mechanisms to avoid battery depletion. + +# Installation + +# Usage + +# Demo file-sharing application + +# Others + +# How to make contributions +Please read and follow the steps in [CONTRIBUTING.md](/CONTRIBUTING.md) + +# License + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +# Acknowledgment + +This software is part of the NGI Pointer project "Incentivised Content Dissemination at the Network Edge" that has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 871528 + +

ngi logo eu logo

+ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..33c915e --- /dev/null +++ b/build.gradle @@ -0,0 +1,32 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.google.gms:google-services:4.0.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + maven { + url 'https://maven.google.com/' + } + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..294111e --- /dev/null +++ b/compile.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +./gradlew installDebug +for var in "$@" +do +# adb -s $var shell am start -n io.fluentic.ubicdn/.ui.splash.SplashActivity +# adb -s $var shell am startservice -n io.fluentic.ubicdn/.data.UbiCDNService --ez "source" 0 --ez "wd" 0 --ez "bt" 1 + adb -s $var shell am start -n network.datahop.localsharing/.ui.splash.SplashActivity +done diff --git a/connectivity.png b/connectivity.png new file mode 100644 index 0000000000000000000000000000000000000000..d96eff5352765904a7a1ce3620fb0b496ff92a48 GIT binary patch literal 83570 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 vG+|LIOcyK&2aQRrr13IO^r-oBxDF&8aGV?mRVX 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 QBJk5xyRBMs-7@6=0C098WdHyG literal 0 HcmV?d00001 diff --git a/demo-filesharing/.gitignore b/demo-filesharing/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/demo-filesharing/.gitignore @@ -0,0 +1 @@ +/build diff --git a/demo-filesharing/app.iml b/demo-filesharing/app.iml new file mode 100644 index 0000000..e964cb9 --- /dev/null +++ b/demo-filesharing/app.iml @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo-filesharing/build.gradle b/demo-filesharing/build.gradle new file mode 100644 index 0000000..4186ea5 --- /dev/null +++ b/demo-filesharing/build.gradle @@ -0,0 +1,101 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "network.datahop.localsharing" + minSdkVersion 24 + targetSdkVersion 28 + versionCode 37 + versionName "3.7" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + // Enables code shrinking, obfuscation, and optimization for only + // your project's release build type. + minifyEnabled true + + // Enables resource shrinking, which is performed by the + // Android Gradle plugin. + shrinkResources true + + // Includes the default ProGuard rules files that are packaged with + // the Android Gradle plugin. To learn more, go to the section about + // R8 configuration files. + proguardFiles getDefaultProguardFile( + 'proguard-android-optimize.txt'), + 'proguard-rules.pro' + } + + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + /*dexOptions { + incremental true + javaMaxHeapSize "4g" + }*/ + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + } + lintOptions { + checkReleaseBuilds false + // Or, if you prefer, you can continue to check for errors in release builds, + // but continue the build even when errors are found: + abortOnError false + } +} + + +dependencies { + //Default navigation drawer sample libs + implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' + + implementation 'com.google.android.play:core:1.6.4' + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.android.material:material:1.1.0' + + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.api-client:google-api-client:1.30.8' + + //Added shareit libs + implementation 'com.android.volley:volley:1.1.1' + implementation 'org.nanohttpd:nanohttpd:2.3.1' + implementation 'net.grandcentrix.tray:tray:0.12.0' + + + implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0' + + testImplementation 'junit:junit:4.13' + + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(path: ':localfirst') + +} + +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + def requested = details.requested + if (requested.group == "com.android.support") { + if (!requested.name.startsWith("multidex")) { + details.useVersion "26.+" + } + } + } +} diff --git a/demo-filesharing/proguard-rules.pro b/demo-filesharing/proguard-rules.pro new file mode 100644 index 0000000..c130cb9 --- /dev/null +++ b/demo-filesharing/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-keep class com.google.android.gms.measurement.** { *; } +-dontwarn com.google.android.gms.measurement.** \ No newline at end of file diff --git a/demo-filesharing/src/androidTest/java/network/datahop/localsharing/ExampleInstrumentedTest.java b/demo-filesharing/src/androidTest/java/network/datahop/localsharing/ExampleInstrumentedTest.java new file mode 100644 index 0000000..baffba1 --- /dev/null +++ b/demo-filesharing/src/androidTest/java/network/datahop/localsharing/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package network.datahop.localsharing; + +import android.content.Context; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("network.datahop.shareit", appContext.getPackageName()); + } +} diff --git a/demo-filesharing/src/main/AndroidManifest.xml b/demo-filesharing/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e75cca8 --- /dev/null +++ b/demo-filesharing/src/main/AndroidManifest.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/App.java b/demo-filesharing/src/main/java/network/datahop/localsharing/App.java new file mode 100644 index 0000000..d1d69e6 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/App.java @@ -0,0 +1,40 @@ +package network.datahop.localsharing; + +import android.app.Application; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; +import androidx.lifecycle.ProcessLifecycleOwner; + +import network.datahop.localsharing.utils.G; + +public class App extends Application implements LifecycleObserver { + + public static boolean IS_APP_IN_FOREGROUND = false; + private static final String TAG=Application.class.getSimpleName(); + @Override + public void onCreate() { + super.onCreate(); + G.Log(TAG,"On create"); + + ProcessLifecycleOwner.get().getLifecycle().addObserver(this); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + public void appInForeground(){ + G.Log(TAG,"App foreground"); + IS_APP_IN_FOREGROUND = true; + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + public void appInBackground(){ + G.Log(TAG,"App background"); + IS_APP_IN_FOREGROUND = false; + } + + public boolean isForeground() + { + G.Log(TAG,"isforeground "+IS_APP_IN_FOREGROUND); + return IS_APP_IN_FOREGROUND; + } +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/MainActivity.java b/demo-filesharing/src/main/java/network/datahop/localsharing/MainActivity.java new file mode 100644 index 0000000..5849ac6 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/MainActivity.java @@ -0,0 +1,1165 @@ +package network.datahop.localsharing; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.job.JobScheduler; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.PowerManager; +import android.os.RemoteException; +import android.provider.Settings; +import androidx.fragment.app.Fragment; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.appcompat.app.AlertDialog; +import com.google.android.material.navigation.NavigationView; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import android.text.InputType; +import android.util.Log; +import android.util.Pair; +import android.view.ContextThemeWrapper; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.List; + +import network.datahop.localfirst.LocalFirstSDK; +import network.datahop.localfirst.LocalFirstListener; +import network.datahop.localfirst.data.Group; +import network.datahop.localfirst.data.Content; +import network.datahop.localfirst.data.ContentDatabaseHandler; +import network.datahop.localsharing.ui.GroupActivity; +import network.datahop.localsharing.ui.fragments.GroupsListFragment; +import network.datahop.localsharing.ui.fragments.SettingsFragment; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; +import network.datahop.localsharing.utils.SettingsPreferences; +import network.datahop.localsharing.ui.fragments.detail.OnGroupClickListener; + +import static network.datahop.localfirst.LocalFirstSDK.ACCEPTANCE; +import static network.datahop.localfirst.LocalFirstSDK.ACTION_ACCEPT; +import static network.datahop.localfirst.LocalFirstSDK.ACTION_REJECT; +import static network.datahop.localfirst.LocalFirstSDK.CHECK_STATUS; +import static network.datahop.localfirst.LocalFirstSDK.DIRECT_CONNECTION; +import static network.datahop.localfirst.LocalFirstSDK.DIRECT_CONNECTION_ACCEPTED; +import static network.datahop.localfirst.LocalFirstSDK.DIRECT_CONNECTION_REJECTED; +import static network.datahop.localfirst.LocalFirstSDK.NEW_CONTENT; +//import static network.datahop.localfirst.LocalFirstSDK.RESTART; +//import static network.datahop.localfirst.LocalFirstSDK.STOP; +import static network.datahop.localfirst.LocalFirstSDK.USER; +import static network.datahop.localfirst.LocalFirstSDK.NOT_SUPPORTED; + +public class MainActivity extends AppCompatActivity + implements NavigationView.OnNavigationItemSelectedListener, OnGroupClickListener { + + + + private static final int PERMISSION_REQUEST_FINE_LOCATION = 1; + private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 2; + private static final int PERMISSION_WIFI_STATE = 3; + //private static final int PERMISSION_REQUEST_WIFI_CHANGE = 4; + private static final String TAG = "MainActivity"; + + /** Client Message Handler */ + //private final Messenger m_clientMessenger = new Messenger(new ClientHandler()); + /** Messenger connection to DataHop Service */ + //private Messenger m_serviceMessenger = null; + /** Flag that marks that application is connected to the DataHop Service */ + //private boolean m_isServiceConnected = false; + + BroadcastReceiver mBroadcastReceiver; + SettingsPreferences timers; + private ClientHandler mHandler; + + ContentDatabaseHandler db; + JobScheduler tm; + MainActivity that; + String name; + Messenger messengerIncoming,messengerAdvIncoming; + boolean resumed=true; + boolean groupActivityStarted=false; + @Override + protected void onCreate(Bundle savedInstanceState) { + + //Disable bluetooth + + if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + { + G.Log(TAG,"Android pie"); + if(!isLocnEnabled(getApplicationContext())) + startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } + + try { + + + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (!mBluetoothAdapter.isEnabled()) { + mBluetoothAdapter.enable(); + } + WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + if (!wifiManager.isWifiEnabled()) wifiManager.setWifiEnabled(true); + }catch (NullPointerException e){ + network.datahop.localfirst.utils.G.Log(TAG,"Null pointer exception"); + Toast.makeText(this, "Unable to start service", Toast.LENGTH_SHORT).show(); + return; + } + + timers = new SettingsPreferences(getApplicationContext()); + mHandler = new ClientHandler(this); + //mServiceComponent = new ComponentName(this, DiscoveryService.class); + //mAdvServiceComponent = new ComponentName(this,AdvertisingService.class); + db = new ContentDatabaseHandler(this); + /*Group group = new Group(); + group.setGroupId(1); + group.setName("testgroup"); + group.setTimestamp(System.currentTimeMillis()); + db.addGroup(group);*/ + + //Messenger messengerIncoming = new Messenger(mHandler); + + tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); + that = this; + + initNotificationChannel(); + + + + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + G.Log(TAG, "Broadcast received " + intent); + Bundle extras; + switch (intent.getAction()) { + case DIRECT_CONNECTION: + extras = intent.getExtras(); + if (extras != null) { + if (extras.containsKey("user")) { + G.Log(TAG,"Direct connection received "+extras.getString("user")); + + if(((App)getApplication()).isForeground()) { + acceptConnection(extras.getString("user")); + } else { + acceptanceNotification(extras.getString("user")); + } + } + } + break; + case NOT_SUPPORTED: + extras = intent.getExtras(); + if (extras != null) { + if (extras.containsKey("message")) { + Toast.makeText(getApplicationContext(), intent.getStringExtra("message"), Toast.LENGTH_SHORT).show(); + } + } + break; + /*case DiscoveryService.DISCOVERED: + case DiscoveryService.SAME_DISCOVERED: + //case TransferFunds.FUNDS_RESULTS: + + extras = intent.getExtras(); + if (extras != null) { + if (extras.containsKey("message")) { + Toast.makeText(getApplicationContext(), intent.getStringExtra("message"), Toast.LENGTH_SHORT).show(); + } + } + break; + */ + /*case DataSharingClient.DOWNLOAD_COMPLETED: + G.Log(TAG,"Download completed"); + //Fragment fragment = MainFragment.newInstance(); + Fragment fragment = GroupsListFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, "MainFragment") + .commit(); + break;*/ + /*if(getSupportFragmentManager().getPrimaryNavigationFragment() instanceof MainFragment) + { + MainFragment fragment = (MainFragment) getSupportFragmentManager().getPrimaryNavigationFragment(); + fragment.refreshAdapter(); + } + break; + case DataSharingClient.NEW_VIDEO_RECEIVED: + case DataSharingClient.NEW_CHUNK_RECEIVED: + G.Log(TAG,"New chunk received"); + /*extras = intent.getExtras(); + //Toast.makeText(getApplicationContext(), "File reception complete.", Toast.LENGTH_SHORT).show(); + if (extras != null) { + if (extras.containsKey("message")) { + G.Log(TAG,intent.getStringExtra("message")); + Toast.makeText(getApplicationContext(), intent.getStringExtra("message"), Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(getApplicationContext(), "File reception complete.", Toast.LENGTH_SHORT).show(); + }*/ + + /*MainFragment mainFragment = (MainFragment) getSupportFragmentManager().findFragmentByTag("MainFragment"); + + if (mainFragment != null && mainFragment.isVisible()) { + G.Log(TAG,"Fragment active"); + mainFragment.refreshAdapter(); + } + else { + G.Log(TAG,"Fragment not active"); + } + break; + case CLEAR_VIDEOS: + Fragment fragment2 = GroupsListFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment2, "MainFragment") + .commit(); + break; + + case AdvertisingService.NOT_SUPPORTED: + extras = intent.getExtras(); + if (extras != null) { + if (extras.containsKey("message")) { + Toast.makeText(getApplicationContext(), intent.getStringExtra("message"), Toast.LENGTH_SHORT).show(); + } + } + break; + */ + } + } + }; + + + + G.Log(TAG,Build.VERSION.SDK_INT+" "+Build.VERSION_CODES.M); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) + setContentView(R.layout.activity_main); + else + setContentView(R.layout.activity_main_legacy); + + + Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setTitle("Groups"); + setSupportActionBar(toolbar); + //getSupportActionBar().setTitle("Meeting Id: "+timers.getMeetingId()); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + // StatsHandler st = new StatsHandler(getApplicationContext()); + + View hView = navigationView.getHeaderView(0); + TextView nav_user = (TextView)hView.findViewById(R.id.userview); + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("pref",Context.MODE_PRIVATE); + String defaultName = getResources().getString(R.string.user_name); + name = sharedPref.getString(getString(R.string.user_name), defaultName); + + try{ + nav_user.setText(name); + }catch (Exception e){G.Log(TAG, "no username ");} + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestForPermissions(); + } + LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mBroadcastReceiver, getIntentFilter()); + LocalFirstSDK.init(getApplicationContext()); + if(timers.getStoragePermission()&&timers.getStoragePermission())startService(); + //startService(); + super.onCreate(savedInstanceState); + + + + } + + private void requestForPermissions() + { + if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_FINE_LOCATION); + } + }); + builder.show(); + } + if (this.checkSelfPermission(Manifest.permission.CHANGE_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.CHANGE_WIFI_STATE}, PERMISSION_WIFI_STATE); + } + }); + builder.show(); + } + if (this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); + } + }); + builder.show(); + } + /*if (this.checkSelfPermission(Manifest.permission.CHANGE_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.CHANGE_WIFI_STATE}, PERMISSION_REQUEST_WIFI_CHANGE); + } + }); + builder.show(); + } + if (this.checkSelfPermission(Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.ACCESS_WIFI_STATE}, PERMISSION_REQUEST_WIFI_STATE); + } + }); + builder.show(); + }*/ + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + G.Log(TAG,"Permissions "+requestCode+" "+permissions+" "+grantResults); + switch (requestCode) { + case PERMISSION_REQUEST_FINE_LOCATION: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted, yay! Do the + // contacts-related task you need to do. + G.Log(TAG,"Location accepted"); + timers.setLocationPermission(true); + if(timers.getStoragePermission())startService(); + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + G.Log(TAG,"Location not accepted"); + + } + break; + } + case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE: + + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted, yay! Do the + // contacts-related task you need to do. + G.Log(TAG,"Storage accepted"); + timers.setStoragePermission(true); + //new CreateWallet(getApplicationContext()).execute(); + if(timers.getLocationPermission())startService(); + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + G.Log(TAG,"Storage not accepted"); + + } + } + + // other 'case' lines to check for other + // permissions this app might request. + + } + + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + super.onBackPressed(); + } + } + + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(MenuItem item) { + + + int id = item.getItemId(); + + + if (id == R.id.nav_content) { + + Fragment fragment = GroupsListFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, "MainFragment") + .commit(); + GroupsListFragment group = (GroupsListFragment)fragment; + group.setOnChatGroupClickListener(this); + // Handle the camera action + /*} else if (id == R.id.nav_status) { + Fragment fragment = ServiceFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, "ServiceFragment") + .commit(); + } else if (id == R.id.nav_wallet) { + Fragment fragment = WalletFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, "WalletFragment") + .commit(); + + */} else if (id == R.id.nav_settings) { + Fragment fragment = SettingsFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, "SettingsFragment") + .commit(); + + } else if (id == R.id.nav_quit) { + //unbindService(); + stopServices(); + finishAffinity(); + finishAndRemoveTask(); + this.finish(); + moveTaskToBack(true); + } else if (id == R.id.nav_share) { + try { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, R.string.app_name); + String shareMessage= "\nLet me recommend you this application\n\n"; + shareMessage = shareMessage + "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID +"\n\n"; + shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage); + startActivity(Intent.createChooser(shareIntent, "choose one")); + //return true; + } catch(Exception e) { + e.toString(); + } + + } + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } + + @Override + protected void onResume(){ + G.Log(TAG,"onResume"); + + //GetLatestVersion checkVersion = new GetLatestVersion(this); + resumed=true; + + //bindService(); + //if(timers.getStoragePermission()&&timers.getStoragePermission())startService(); + super.onResume(); + + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + G.Log(TAG, "onNewIntent"); + Bundle extras = intent.getExtras(); + int tabNumber; + + if (extras != null) { + tabNumber = extras.getInt(ACCEPTANCE); + G.Log(TAG, "Tab Number: " + tabNumber + " " + extras.get(USER)); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + if (tabNumber == 0) + acceptConnection(extras.getString(USER)); + +// notificationId is a unique int for each notification that you must define + notificationManager.cancel(0); + + } else { + Log.d(TAG, "Extras are NULL"); + + } + } + + @Override + protected void onPause(){ + G.Log(TAG,"onPause"); + resumed=false; + //unbindService(); + //G.Log(TAG,"Starting service "+timers.isScanning()); + /*if(timers.isScanning()) { + Intent startServiceIntent = new Intent(this, DiscoveryService.class); + Messenger messengerIncoming = new Messenger(mHandler); + startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming); + startService(startServiceIntent); + scheduleJob(timers.getBtIdleBgTime()); + }*/ + super.onPause(); + + } + + @Override + protected void onDestroy() { + // A service can be "started" and/or "bound". In this case, it's "started" by this Activity + // and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call + // to stopService() won't prevent scheduled jobs to be processed. However, failing + // to call stopService() would keep it alive indefinitely. + G.Log(TAG,"ondestroy"); + LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(mBroadcastReceiver); + + stopServices(); + super.onDestroy(); + } + + @Override + protected void onStart() { + super.onStart(); + // Start service and provide it a way to communicate with this class. + setMainFragment(); + } + + + @Override + public void onGroupClicked(Group chatGroup, int position) { + G.Log(TAG,"Group clicked "+db.getGroupName(position)+" "+position); + groupActivityStarted=true; + db.groupClearPending(chatGroup.getName()); + /*Intent intent = new Intent(MainActivity.this, GroupActivity.class); + intent.putExtra("group", db.getGroupName(position)); + startActivityForResult(intent,1);*/ + Intent intent = new Intent(that, GroupActivity.class); + intent.putExtra("group", db.getGroupName(position+1)); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); + } + + + @Override + public void onGroupLongClicked(Group chatGroup, int position) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Remove all groups?"); + // Set up the buttons + builder.setMessage(R.string.action_delete_group); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteGroup(chatGroup); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + } + + public void stopServices(){ + G.Log(TAG,"Stop services"); + //tm.cancelAll(); + //Intent broadcast = new Intent(STOP); + //LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast); + /*Message m = Message.obtain(); + m.what = 4; + try{messengerAdvIncoming.send(m);}catch (RemoteException e){G.Log(TAG,"Remote exception "+e);} + Message m2 = Message.obtain(); + m.what = 4; + try{messengerIncoming.send(m2);}catch (RemoteException e){G.Log(TAG,"Remote exception "+e);}*/ + LocalFirstSDK.stop(getApplicationContext()); + //stopService(new Intent(this, DiscoveryService.class)); + //stopService(new Intent(this, AdvertisingService.class)); + + } + + + + /** + * Method that binds the current activity to the DataHop Service. + */ + /*private void + bindService() { + if (!m_isServiceConnected) { + // Bind to Service + bindService(new Intent(this, DataHopConnectivityService.class), + m_ServiceConnection, Context.BIND_AUTO_CREATE); + G.Log(TAG,"bindService()"); + } + }*/ + + /** + * Method that unbinds the current activity from the DataHop Service. + */ + /* private void + unbindService() { + if (m_isServiceConnected) { + // Unbind from Service + unbindService(m_ServiceConnection); + + m_isServiceConnected = false; + + G.Log(TAG,"unbindService()"); + } + + }*/ + + /** + * Client ServiceConnection to DataHop Service. + */ + /*public final ServiceConnection m_ServiceConnection = new ServiceConnection() { + @Override + public void + onServiceConnected(ComponentName className, IBinder service) { + // Establish Messenger to the Service + m_serviceMessenger = new Messenger(service); + m_isServiceConnected = true; // onServiceConnected runs on the main thread + G.Log(TAG,"Connected"); + // Check if DataHop Service is running + try { + Message msg = Message.obtain(null, DataHopConnectivityService.CHECK_LOGIN); + msg.replyTo = m_clientMessenger; + G.Log(TAG,"message "+msg.replyTo); + m_serviceMessenger.send(msg); + } catch (RemoteException e) { + // If Service crashes, nothing to do here + G.Log(TAG,"onServiceConnected(): " + e); + } + + G.Log(TAG,"m_ServiceConnection::onServiceConnected()"); + } + + @Override + public void + onServiceDisconnected(ComponentName componentName) { + // In event of unexpected disconnection with the Service; Not expecting to get here. + G.Log(TAG,"m_ServiceConnection::onServiceDisconnected()"); + m_isServiceConnected = false; // onServiceDisconnected runs on the main thread + } + };*/ + + private void refresh() + { + //Intent broadcast = new Intent(RESTART); + //LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast); + GroupsListFragment fragment = (GroupsListFragment)getSupportFragmentManager().findFragmentByTag("MainFragment"); + if (fragment != null && fragment.isVisible()) { + G.Log(TAG,"Group list fragment"); + fragment.updateChatGroupsListAdapter(db.getGroups()); + } + } + + private class ClientHandler extends Handler { + + // Prevent possible leaks with a weak reference. + private WeakReference mActivity; + + ClientHandler(MainActivity activity) { + super(/* default looper */); + this.mActivity = new WeakReference<>(activity); + } + @Override + public void handleMessage(Message msg) { + MainActivity mainActivity = mActivity.get(); + if (mainActivity == null) { + // Activity is no longer available, exit. + return; + } + Pair messagePair = (Pair) msg.obj; + App app = (App)getApplication(); + G.Log(TAG,"ClientHandler: "+msg.what+" "+messagePair.first+" "+messagePair.second+" "+!app.isForeground()); + + switch (msg.what) { + case CHECK_STATUS: + + break; + + //case NEW_CHUNK: + case NEW_CONTENT: + if(!app.isForeground()) + { + sendNotification(messagePair.first,messagePair.second); + } + + db.groupIncreasePending(messagePair.second); + + GroupsListFragment fragment = (GroupsListFragment)getSupportFragmentManager().findFragmentByTag("MainFragment"); + + if (fragment != null && fragment.isVisible()) { + G.Log(TAG,"Group list fragment "+db.getGroupPending(messagePair.second)); + fragment.updateChatGroupsListAdapter(db.getGroups()); + } + + break; + + /*case DataHopConnectivityService.LOGIN_OK: + //hideProgressDialog(); + //startApp(); + G.Log(TAG,"ClientHandler: DataHop service is Running."); + break; + + case DataHopConnectivityService.LOGIN_KO: + //hideProgressDialog(); + //Toast.makeText(getApplicationContext(), "Authentication failed", + // Toast.LENGTH_SHORT).show(); + G.Log(TAG,"ClientHandler: DataHop service is Stopped."); + break; + + case DataHopConnectivityService.LOGOUT_OK: + + G.Log(TAG,"ClientHandler: logout."); + //Fragment + //backToLogIn(); + break;*/ + default: + super.handleMessage(msg); + break; + } + } + public void + replyToClient(Message message, int replyMessage) { + try { + message.replyTo.send(Message.obtain(null, replyMessage)); + } catch (RemoteException e) { + // Nothing to do here; It means that client end has been terminated. + } + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_clear) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Remove all groups?"); + // Set up the buttons + builder.setMessage(R.string.action_delete); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteGroups(); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + // return true; + } else if (id == R.id.action_meeting) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Group name"); + + // Set up the input + final EditText input = new EditText(this); + // Specify the type of input expected; this, for example, sets the input as a password, and will mask the text + input.setInputType(InputType.TYPE_CLASS_TEXT); + //input.setText(timers.getMeetingId()); + builder.setView(input); + + // Set up the buttons + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //timers.setMeetingId(input.getText().toString()); + //getSupportActionBar().setTitle("Meeting Id: "+timers.getMeetingId()); + //startService(); + G.Log(TAG,"ADD group "+db.getGroupMaxId()); + Group g = new Group(); + g.setGroupId(db.getGroupMaxId()+1); + g.setName(input.getText().toString()); + g.setTimestamp(System.currentTimeMillis()); + if(!db.getGroups().contains(g)) + db.addGroup(g); + else + Toast.makeText(getApplicationContext(), "Group already exists", Toast.LENGTH_SHORT).show(); + refresh(); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + // return true; + } + + return super.onOptionsItemSelected(item); + } + + + private void setMainFragment(){ + Fragment fragment = GroupsListFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, "MainFragment") + .commit(); + GroupsListFragment group = (GroupsListFragment)fragment; + group.setOnChatGroupClickListener(this); + + } + + public void startService() + { + + LocalFirstSDK.start(getApplicationContext(),name,timers.isScanning(),timers.getBtScanTime(),timers.getBtIdleFgTime(),timers.getBtIdleBgTime(),timers.getHotspotRestartTime(), new LocalFirstListener(){ + @Override + public void newFileReceived(String name) { + G.Log(TAG,"New file received "+name); + } + + @Override + public void newUSerDiscovered(String name) { + G.Log(TAG,"New user discovered "+name); + + } + + @Override + public void newDataDiscovered(String data) { + + } + }); + } + + + private void deleteGroups(){ + + File dir = this.getExternalFilesDir(Config.FOLDER); + File[] subFiles = dir.listFiles(); + + for(Group group : db.getGroups() ) + { + //if(group.getGroupId()>0) { + for(Content content : db.getGroupContent(group.getName())) { + // Writing Contacts to log + String name = content.getName(); + if (content.getId() > 0) name += "." + content.getId(); + //G.Log(TAG,"Name:"+ name+ " desc:" + cn.getText() + " url:"+cn.getUrl()); + if (subFiles != null) { + //G.Log("Files " +subFiles); + for (File file : subFiles) + { + // G.Log("Filename " + cn.getName() + " " +file.getAbsolutePath()+" "+file.getName()+" "+file.length()); + if (file.getName().equals(name) || file.getName().equals(content.getName())) + file.delete(); + } + } + db.rmContent(content.getUri(), group.getName()); + + } + db.rmGroup(group.getName()); + //} + } + /*String action = DataSharingClient.DOWNLOAD_COMPLETED; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast);*/ + refresh(); + + } + + private void deleteGroup(Group group) + { + File dir = this.getExternalFilesDir(Config.FOLDER); + File[] subFiles = dir.listFiles(); + + if(group.getGroupId()>0) { + for(Content content : db.getGroupContent(group.getName())) { + // Writing Contacts to log + String name = content.getName(); + if (content.getId() > 0) name += "." + content.getId(); + //G.Log(TAG,"Name:"+ name+ " desc:" + cn.getText() + " url:"+cn.getUrl()); + if (subFiles != null) { + //G.Log("Files " +subFiles); + for (File file : subFiles) + { + // G.Log("Filename " + cn.getName() + " " +file.getAbsolutePath()+" "+file.getName()+" "+file.length()); + if (file.getName().equals(name) || file.getName().equals(content.getName())) + file.delete(); + } + } + db.rmContent(content.getUri(), group.getName()); + + } + db.rmGroup(group.getName()); + } + + /*String action = DataSharingClient.DOWNLOAD_COMPLETED; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast);*/ + refresh(); + } + + public void initNotificationChannel() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return; + } + + + // Create channel to show notifications. + String channelId = getString(R.string.default_notification_channel_id); + String channelName = getString(R.string.default_notification_channel_name); + NotificationManager notificationManager = + getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(new NotificationChannel(channelId, + channelName, NotificationManager.IMPORTANCE_LOW)); + + + } + + public void acceptanceNotification(final String msg){ + + /*Intent mIntentReject = new Intent(this, MainActivity.class); + Intent snoozeIntent = new Intent(this, MainActivity.class); + snoozeIntent.setAction(ACCEPTANCE); + snoozeIntent.putExtra(EXTRA_NOTIFICATION_ID, 0); + PendingIntent snoozePendingIntent = + PendingIntent.getBroadcast(this, 0, snoozeIntent, 0);*/ + + Intent intentAccept = new Intent(this, MainActivity.class); + intentAccept.setAction(ACTION_ACCEPT); + intentAccept.putExtra(ACCEPTANCE, 0); + intentAccept.putExtra(USER,msg); + G.Log(TAG,"accept notification "+msg+" "+intentAccept.getExtras().getString(USER)); + + + PendingIntent pIntentAccept = PendingIntent.getActivity(this, 0, intentAccept, 0); + intentAccept.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intentAccept.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + Intent intentReject = new Intent(this, MainActivity.class); + intentReject.setAction(ACTION_REJECT); + intentReject.putExtra(ACCEPTANCE, 1); + PendingIntent pIntentReject = PendingIntent.getActivity(this, 0, intentReject, 0); + intentReject.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intentReject.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(R.mipmap.ic_stat_looks) + .setContentTitle("Accept connection?") + .setChannelId(getString(R.string.default_notification_channel_id)) + .addAction(R.drawable.ic_icon,"Accept",pIntentAccept) + .addAction(R.drawable.ic_icon,"Decline",pIntentReject) + .setAutoCancel(true) + .setContentText("User "+msg+" is trying to create a group with you."); + + /*TaskStackBuilder stackBuilder = TaskStackBuilder.create(getApplicationContext()); + stackBuilder.addNextIntent(intent); + PendingIntent resultPendingIntent = stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + );*/ + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + +// notificationId is a unique int for each notification that you must define + notificationManager.notify(0, mBuilder.build()); + +/* +// build notification +// the addAction re-use the same intent to keep the example short + Notification n = new Notification.Builder(this) + .setContentTitle("Accept connection?") + .setContentText("User "+msg+" is trying to create a group with you.") + .setSmallIcon(R.mipmap.ic_stat_looks) + .setContentIntent(pIntent) + .setAutoCancel(true) + .setActions() + .addAction(R.drawable.ic_icon, "Accept", pIntent) + .addAction(R.drawable.ic_icon, "Decline", pIntent).build(); + //.addAction(R.drawable.ic_icon, "See", pIntent).build(); + + + NotificationManager notificationManager = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + notificationManager.notify(0, n);*/ + + // Turn on the screen for notification + PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); + boolean result= Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT_WATCH&&powerManager.isInteractive()|| Build.VERSION.SDK_INT< Build.VERSION_CODES.KITKAT_WATCH&&powerManager.isScreenOn(); + + if (!result){ + @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK |PowerManager.ACQUIRE_CAUSES_WAKEUP |PowerManager.ON_AFTER_RELEASE,"MH24_SCREENLOCK"); + wl.acquire(10000); + @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl_cpu = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MH24_SCREENLOCK"); + wl_cpu.acquire(10000); + } + } + + public void sendNotification(final String msg, final String group){ + + + Intent intent = new Intent(this, MainActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(R.mipmap.ic_stat_looks) + .setContentTitle("New file received at group "+group) + .setChannelId(getString(R.string.default_notification_channel_id)) + .setAutoCancel(true) + .setContentIntent(pIntent) + .setContentText(msg); + + /*TaskStackBuilder stackBuilder = TaskStackBuilder.create(getApplicationContext()); + stackBuilder.addNextIntent(intent); + PendingIntent resultPendingIntent = stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + );*/ + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + +// notificationId is a unique int for each notification that you must define + notificationManager.notify(1, builder.build()); + + // Turn on the screen for notification + PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); + boolean result= Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT_WATCH&&powerManager.isInteractive()|| Build.VERSION.SDK_INT< Build.VERSION_CODES.KITKAT_WATCH&&powerManager.isScreenOn(); + + if (!result){ + @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK |PowerManager.ACQUIRE_CAUSES_WAKEUP |PowerManager.ON_AFTER_RELEASE,"MH24_SCREENLOCK"); + wl.acquire(10000); + @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl_cpu = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MH24_SCREENLOCK"); + wl_cpu.acquire(10000); + } + } + + private void acceptConnection(String user) + { + AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(that, R.style.myDialog)); + //AlertDialog.Builder builder = new AlertDialog.Builder(that); + builder.setTitle("Request received"); + // Set up the buttons + builder.setMessage("Do you want to share files with " + user + "?"); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String action = DIRECT_CONNECTION_ACCEPTED; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast); + + //StatsHandler st = new StatsHandler(getApplicationContext()); + Group group = new Group(); + group.setGroupId(db.getGroupMaxId() + 1); + group.setName(name + "-" + user); + G.Log(TAG, "Add group " + System.currentTimeMillis()); + group.setTimestamp(System.currentTimeMillis()); + if (!db.getGroups().contains(group)) + db.addGroup(group); + else + Toast.makeText(getApplicationContext(), "Group already exists", Toast.LENGTH_SHORT).show(); + refresh(); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + String action = DIRECT_CONNECTION_REJECTED; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast); + } + }); + + builder.show(); + } + + private static boolean isLocnEnabled(Context context) { + G.Log(TAG,"Checking location"); + List locnProviders = null; + try { + LocationManager lm =(LocationManager) context.getApplicationContext().getSystemService(Activity.LOCATION_SERVICE); + locnProviders = lm.getProviders(true); + + return (locnProviders.size() != 0); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (BuildConfig.DEBUG) { + if ((locnProviders == null) || (locnProviders.isEmpty())) + G.Log(TAG, "Location services disabled"); + else + G.Log(TAG, "locnProviders: " + locnProviders.toString()); + } + } + return(false); + } + + private IntentFilter getIntentFilter() + { + IntentFilter filter = new IntentFilter(); + //filter.addAction(DiscoveryService.DISCOVERED); + //filter.addAction(DiscoveryService.SAME_DISCOVERED); + //filter.addAction(DataSharingClient.DOWNLOAD_COMPLETED); + //filter.addAction(DataSharingClient.NEW_VIDEO_RECEIVED); + //filter.addAction(DataSharingClient.NEW_CHUNK_RECEIVED); + //filter.addAction(CLEAR_VIDEOS); + filter.addAction(NOT_SUPPORTED); + filter.addAction(DIRECT_CONNECTION); + //filter.addAction(TransferFunds.FUNDS_RESULTS); + return filter; + } + + + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/data/AddFile.java b/demo-filesharing/src/main/java/network/datahop/localsharing/data/AddFile.java new file mode 100644 index 0000000..ea67992 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/data/AddFile.java @@ -0,0 +1,201 @@ +package network.datahop.localsharing.data; + +import android.app.ProgressDialog; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.OpenableColumns; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import network.datahop.localfirst.LocalFirstSDK; +import network.datahop.localsharing.ui.GroupActivity; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; +import network.datahop.localsharing.utils.PathUtil; + +//import static network.datahop.localsharing.MainActivity.RESTART; + +public class AddFile extends AsyncTask { + + public static final String TAG = "AddFile"; + + Context context; + ContentDatabaseHandler db; + boolean finished = false; + ProgressDialog mProgress; + private static GroupActivity parent; + String group; + + public AddFile(Context context, GroupActivity parent,String group) + { + this.context = context; + db = new ContentDatabaseHandler(context); + //this.listener = listener; + this.parent = parent; + this.group = group; + } + + @Override + protected String doInBackground(Uri... params) { + + Uri path = params[0]; + parent.runOnUiThread(new Runnable() { + public void run() { + mProgress = ProgressDialog.show(context, "", "Loading file..", true); + } + }); + return saveFile(path); + } + + @Override + protected void onPostExecute(String name) { + + G.Log(TAG, "Post " + name); + parent.runOnUiThread(new Runnable() { + public void run() { + mProgress.dismiss(); + } + }); + + try { + int total = db.getContent(name, group).get(0).getTotal(); + G.Log(TAG, "Total " + total); + for (int i = 1; i <= total; i++) { + G.Log(TAG, "Set content downloaded " + i + " " + total); + db.setContentDownloaded(name, group, i); + db.setContentJoined(name, group); + } + parent.refreshAdapter(); + + } catch (Exception e){ + G.Log(TAG,"File not added correctly "+e); + } + + } + + @Override + protected void onPreExecute() { + } + + @Override + protected void onProgressUpdate(Void... values) { + } + + private String saveFile(Uri path) { + try { + G.Log(TAG, "Uri " + path.toString() + " " + path.getLastPathSegment()); + String filename = ""; + if (path.toString().startsWith("content")) { + filename = getFileName(path); + G.Log(TAG, "Content " + filename); + + } else { + String file = PathUtil.getPath(context, path); + filename = file.substring(file.lastIndexOf("/") + 1); + G.Log(TAG, "else " + filename); + } + G.Log(TAG, "Filename " + filename+" "+group); + File mFile = new File(context.getExternalFilesDir(Config.FOLDER), filename); + storeFile(path, mFile); + LocalFirstSDK.addFile(context,filename, mFile.getPath(),group); + /*G.Log(TAG, "Filename " + filename+" "+ Chunking.getTotalParts(mFile.getAbsolutePath())+" "+group); + + try { + Chunking.split(mFile.getPath(), group, context); + } catch (IOException f) { + G.Log(TAG, "IO error " + f); + }*/ + + //Intent broadcast = new Intent(RESTART); + //LocalBroadcastManager.getInstance(context).sendBroadcast(broadcast); + return mFile.getName(); + + }catch (Exception e){ + G.Log("Exception "+e); + + } + + return null; + + } + + public String getFileName(Uri uri) { + + // The query, since it only applies to a single document, will only return + // one row. There's no need to filter, sort, or select fields, since we want + // all fields for one document. + Cursor cursor = context.getContentResolver() + .query(uri, null, null, null, null, null); + String displayName = ""; + try { + + if (cursor != null && cursor.moveToFirst()) { + + // Note it's called "Display Name". This is + // provider-specific, and might not necessarily be the file name. + displayName = cursor.getString( + cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + G.Log(TAG, "Display Name: " + displayName); + + int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); + // If the size is unknown, the value stored is null. But since an + // int can't be null in Java, the behavior is implementation-specific, + // which is just a fancy term for "unpredictable". So as + // a rule, check if it's null before assigning to an int. This will + // happen often: The storage API allows for remote files, whose + // size might not be locally known. + String size = null; + if (!cursor.isNull(sizeIndex)) { + // Technically the column stores an int, but cursor.getString() + // will do the conversion automatically. + size = cursor.getString(sizeIndex); + } else { + size = "Unknown"; + } + G.Log(TAG, "Size: " + size); + } + } finally { + cursor.close(); + } + + return displayName; + } + + + public void storeFile(Uri mName, File mFile) { + + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + try { + bis = new BufferedInputStream(context.getContentResolver().openInputStream(mName)); + bos = new BufferedOutputStream(new FileOutputStream(mFile)); + byte[] buf = new byte[1024]; + bis.read(buf); + do { + bos.write(buf); + } while (bis.read(buf) != -1); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + + if (bis != null) bis.close(); + if (bos != null) bos.close(); + finished = true; + + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/data/ContentDatabaseHandler.java b/demo-filesharing/src/main/java/network/datahop/localsharing/data/ContentDatabaseHandler.java new file mode 100644 index 0000000..52c06c1 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/data/ContentDatabaseHandler.java @@ -0,0 +1,699 @@ +/******************************************************* + * Copyright (C) 2020 DataHop Labs Ltd + * + * This file is part of DataHop Network project. + * + * All rights reserved + *******************************************************/ + +package network.datahop.localsharing.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.util.ArrayList; +import java.util.List; + +import network.datahop.localfirst.data.Content; +import network.datahop.localfirst.data.Group; + +public class ContentDatabaseHandler extends SQLiteOpenHelper { + + // All Static variables + // Database Version + private static final int DATABASE_VERSION = 1; + + // Database Name + private static final String DATABASE_NAME = "contentManager"; + + // table name + private static final String TABLE_CONTENT = "content"; + private static final String TABLE_GROUPS = "groups"; + + + // Contacts Table Columns names + private static final String KEY_GROUP_ID = "group_id"; + private static final String KEY_GROUP = "group_name"; + private static final String KEY_GROUP_DESC = "group_desc"; + private static final String KEY_GROUP_IMG = "group_img"; + private static final String KEY_GROUP_TIME = "group_time_created"; + private static final String KEY_GROUP_PENDING = "group_pending"; + + + private static final String KEY_NAME = "name"; + private static final String KEY_DESC = "desc"; + private static final String KEY_URL = "url"; + private static final String KEY_DOWN = "downloaded"; + private static final String KEY_JOIN = "joined"; + private static final String KEY_CHUNK_ID= "chunk_id"; + private static final String KEY_CHUNKS_TOTAL = "chunk_total"; + private static final String KEY_CRC = "CRC"; + private static final String KEY_URI = "uri"; + + public ContentDatabaseHandler(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + //Log.d("DB","ContentDatabaseHandler"); + } + + // Creating Tables + @Override + public void onCreate(SQLiteDatabase db) { + //Log.d("DB","onCreate"); + String CREATE_CONTENT_TABLE = "CREATE TABLE " + TABLE_CONTENT + "(" + + KEY_GROUP + " TEXT NOT NULL," + + KEY_URI + " TEXT NOT NULL," + + KEY_NAME + " TEXT NOT NULL," + + KEY_DESC + " TEXT," + + KEY_URL + " TEXT NOT NULL, " + + KEY_CHUNK_ID + " INT NOT NULL," + + KEY_CHUNKS_TOTAL + " INT NOT NULL, " + + KEY_CRC + " TEXT ," + + KEY_JOIN + " INT NOT NULL," + + KEY_DOWN + " INT NOT NULL, PRIMARY KEY ("+KEY_URI+", "+KEY_CHUNK_ID+", "+KEY_GROUP+"))"; + + String CREATE_GROUPS_TABLE = "CREATE TABLE " + TABLE_GROUPS + "(" + + KEY_GROUP_ID + " INT PRIMARY KEY AUTOINCREMENT," + + KEY_GROUP + " TEXT NOT NULL," + + KEY_GROUP_PENDING + " INT NOT NULL," + + KEY_GROUP_TIME + " BIGINT NOT NULL," + + KEY_GROUP_DESC + " TEXT," + + KEY_GROUP_IMG + " TEXT, PRIMARY KEY ("+KEY_GROUP_ID+"))"; + db.execSQL(CREATE_CONTENT_TABLE); + db.execSQL(CREATE_GROUPS_TABLE); + + } + + // Upgrading database + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // Drop older table if existed + db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTENT); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_GROUPS); + + // Create tables again + onCreate(db); + } + + /** + * All CRUD(Create, Read, Update, Delete) Operations + */ + + // Adding new contact + public synchronized boolean addContent(Content content) { + SQLiteDatabase db = this.getWritableDatabase(); + + String selectQuery = "SELECT COUNT(*) FROM " + TABLE_CONTENT + " WHERE "+KEY_NAME+"='" +content.getName()+"' AND "+KEY_CHUNK_ID+"="+content.getId()+" AND "+KEY_GROUP+"='"+content.getGroup()+"'"; + Cursor cursor = db.rawQuery(selectQuery, null); + int val=0; + if (cursor.moveToFirst()) { + do { + val=cursor.getInt(0); + + } while (cursor.moveToNext()); + } + if(val>0) return false; + ContentValues values = new ContentValues(); + values.put(KEY_GROUP, content.getGroup()); + values.put(KEY_URI, content.getUri()); + values.put(KEY_NAME, content.getName()); + values.put(KEY_DESC, content.getText()); + values.put(KEY_URL, content.getUrl()); + values.put(KEY_CHUNK_ID,content.getId()); + values.put(KEY_CHUNKS_TOTAL,content.getTotal()); + values.put(KEY_CRC,content.getCrc()); + values.put(KEY_JOIN, 0); + values.put(KEY_DOWN, 0); + // Inserting Row + db.insert(TABLE_CONTENT, null, values); + db.close(); // Closing database connection + + return true; + } + + public void rmContent(String uri,String group) { + SQLiteDatabase db = this.getWritableDatabase(); + db.delete(TABLE_CONTENT, KEY_URI + " = ? AND " + KEY_GROUP + "= ?", + new String[] {uri,group}); + + db.close(); + } + + public void rmGroup(String group) { + SQLiteDatabase db = this.getWritableDatabase(); + db.delete(TABLE_GROUPS, KEY_GROUP + " = ?", + new String[] {group}); + + db.close(); + } + + + public List getContent(String uri){ + List contentList = new ArrayList<>(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=1 AND "+KEY_NAME+"='"+uri+"'"; + //Log.d("DB","get content "); + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + // content.setID(Integer.parseInt(cursor.getString(0))); + content.setGroup(cursor.getString(0)); + content.setUri(cursor.getString(1)); + content.setName(cursor.getString(2)); + content.setText(cursor.getString(3)); + content.setUrl(cursor.getString(4)); + content.setId(Integer.parseInt(cursor.getString(5))); + content.setTotal(Integer.parseInt(cursor.getString(6))); + content.setCrc(cursor.getString(7)); + //Log.d("DB","get content "+ content.getName()+" "+content.getId()); + + contentList.add(content); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + } + + + public synchronized int setContentDownloaded(String uri, String group, int id) + { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + + values.put(KEY_DOWN, 1); + + // updating row + return db.update(TABLE_CONTENT, values, KEY_URI + " = ? AND " + KEY_CHUNK_ID + "= ? AND " + KEY_GROUP + "= ?", + new String[] {uri,String.valueOf(id),group}); + } + + public synchronized int setContentJoined(String uri, String group) + { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + + values.put(KEY_JOIN, 1); + + // updating row + return db.update(TABLE_CONTENT, values, KEY_URI + " = ? AND " + KEY_GROUP + "= ?", + new String[] {uri,group}); + } + + public boolean isContentJoined(String name, String group){ + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=1 AND "+KEY_GROUP+"='"+group+"' AND "+KEY_NAME+"='"+name+"'"; + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + if (cursor.moveToFirst()) { + int joined = cursor.getInt(8); + db.close(); + if(joined==1)return true; + else return false; + } + db.close(); + return false; + } + + + public List getGroups() + { + //Log.d("DB","get group"); + List groupList = new ArrayList<>(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_GROUPS; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + // Log.d("DB","get group2"); + Group group = new Group(); + group.setGroupId(cursor.getInt(0)); + // content.setID(Integer.parseInt(cursor.getString(0))); + group.setName(cursor.getString(1)); + group.setPending(cursor.getInt(2)); + group.setIconURL(cursor.getString(4)); + group.setTimestamp(cursor.getLong(5)); + groupList.add(group); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return groupList; + } + + // Adding new contact + public synchronized boolean addGroup(Group group) { + SQLiteDatabase db = this.getWritableDatabase(); + //Log.d("DB","add group"); + + String selectQuery = "SELECT COUNT(*) FROM " + TABLE_GROUPS + " WHERE "+KEY_GROUP_ID+"='" +group.getGroupId()+"'"; + Cursor cursor = db.rawQuery(selectQuery, null); + int val=0; + if (cursor.moveToFirst()) { + do { + val=cursor.getInt(0); + //Log.d("DB","add group2 "+val); + } while (cursor.moveToNext()); + } + if(val>0) return false; + ContentValues values = new ContentValues(); + //values.put(KEY_GROUP_ID, group.getGroupId()); + values.put(KEY_GROUP, group.getName()); + values.put(KEY_GROUP_DESC,""); + values.put(KEY_GROUP_PENDING,0); + values.put(KEY_GROUP_IMG, group.getIconURL()); + // Inserting Row + values.put(KEY_GROUP_TIME,group.getCreatedOn()); + db.insert(TABLE_GROUPS, null, values); + db.close(); // Closing database connection + + return true; + } + + public String getGroupName(int id) + { + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_GROUPS + " WHERE "+KEY_GROUP_ID+"="+id; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + return cursor.getString(1); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return ""; + } + + public Group getGroup(String name) + { + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_GROUPS + " WHERE "+KEY_GROUP+"='"+name+"'"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + Group group = new Group(); + + if (cursor.moveToFirst()) { + do { + // Log.d("DB","get group2"); + group.setGroupId(cursor.getInt(0)); + // content.setID(Integer.parseInt(cursor.getString(0))); + group.setName(cursor.getString(1)); + group.setPending(cursor.getInt(2)); + group.setIconURL(cursor.getString(4)); + group.setTimestamp(cursor.getLong(5)); + //groupList.add(group); + } while (cursor.moveToNext()); + cursor.close(); + db.close(); // Closing database connection + // return contact list + return group; + } else { + cursor.close(); + db.close(); // Closing database connection + return null; + } + + } + + + public int getGroupPending(String name) + { + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_GROUPS + " WHERE "+KEY_GROUP+"='"+name+"'"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + return cursor.getInt(2); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return 0; + } + + public synchronized int groupIncreasePending(String name) + { + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_GROUPS + " WHERE "+KEY_GROUP+"='"+name+"'"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + int pending=0; + if (cursor.moveToFirst()) { + do { + pending = cursor.getInt(2); + } while (cursor.moveToNext()); + } + cursor.close(); + pending++; + + ContentValues values = new ContentValues(); + + values.put(KEY_GROUP_PENDING, pending); + + // updating row + return db.update(TABLE_GROUPS, values, KEY_GROUP + " = ?", + new String[] {name}); + // return contact list + } + + public synchronized int groupClearPending(String name) + { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + + values.put(KEY_GROUP_PENDING, 0); + + // updating row + return db.update(TABLE_GROUPS, values, KEY_GROUP + " = ?", + new String[] {name}); + } + + public int getGroupMaxId() + { + // Select All Query + String selectQuery = "SELECT MAX("+KEY_GROUP_ID+") FROM " + TABLE_GROUPS; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + return cursor.getInt(0); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return -1; + } + + public boolean checkContent(String name, int id){ + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "/*+KEY_DOWN+"=1 AND "*/+KEY_CHUNK_ID+"="+id+" AND "+KEY_NAME+"='"+name+"'"; + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + if (cursor.moveToFirst()) { + db.close(); + return true; + } + db.close(); + return false; + } + + public List getPendingContent(String group) + { + List contentList = new ArrayList<>(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=0 AND "+KEY_CHUNK_ID+"=0 AND "+KEY_GROUP+"='"+group+"'"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + content.setGroup(cursor.getString(0)); + // content.setID(Integer.parseInt(cursor.getString(0))); + content.setUri(cursor.getString(1)); + content.setName(cursor.getString(2)); + content.setText(cursor.getString(3)); + content.setUrl(cursor.getString(4)); + content.setId(Integer.parseInt(cursor.getString(5))); + content.setTotal(Integer.parseInt(cursor.getString(6))); + content.setCrc(cursor.getString(7)); + contentList.add(content); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + } + + public List getContentDownloaded(String group) + { + List contentList = new ArrayList(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=1 AND "+KEY_GROUP+"='"+group+"' AND "+KEY_CHUNK_ID+"!=0"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + content.setGroup(cursor.getString(0)); + content.setUri(cursor.getString(1)); + content.setName(cursor.getString(2)); + content.setText(cursor.getString(3)); + content.setUrl(cursor.getString(4)); + content.setId(Integer.parseInt(cursor.getString(5))); + content.setTotal(Integer.parseInt(cursor.getString(6))); + content.setCrc(cursor.getString(7)); + //Log.d("DB","get content "+ content.getName()+" "+content.getId()); + + contentList.add(content); + + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + } + + public List getContentDownloaded() + { + List contentList = new ArrayList(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=1 AND "+KEY_CHUNK_ID+"!=0"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + content.setGroup(cursor.getString(0)); + content.setUri(cursor.getString(1)); + content.setName(cursor.getString(2)); + content.setText(cursor.getString(3)); + content.setUrl(cursor.getString(4)); + content.setId(Integer.parseInt(cursor.getString(5))); + content.setTotal(Integer.parseInt(cursor.getString(6))); + content.setCrc(cursor.getString(7)); + contentList.add(content); + + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + } + + /*public List getContentDownloaded() + { + List contentList = new ArrayList(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=1 AND "+KEY_CHUNK_ID+"!=0"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + // content.setID(Integer.parseInt(cursor.getString(0))); + content.setUri(cursor.getString(0)); + content.setName(cursor.getString(1)); + content.setText(cursor.getString(2)); + content.setUrl(cursor.getString(3)); + content.setId(Integer.parseInt(cursor.getString(4))); + content.setTotal(Integer.parseInt(cursor.getString(5))); + content.setCrc(cursor.getString(6)); + contentList.add(content); + + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + } + + public List getMainContentDownloaded() + { + List contentList = new ArrayList(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=1 AND "+KEY_CHUNK_ID+"=0"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + //G.Log("Content "+cursor.getString(0)+" "+cursor.getString(1)+" "+cursor.getString(2)+" "+cursor.getString(3)+" "+cursor.getString(4)+" "+cursor.getString(5)); + Content content = new Content(); + content.setUri(cursor.getString(0)); + content.setName(cursor.getString(1)); + content.setText(cursor.getString(2)); + content.setUrl(cursor.getString(3)); + content.setId(Integer.parseInt(cursor.getString(4))); + content.setTotal(Integer.parseInt(cursor.getString(5))); + content.setCrc(cursor.getString(6)); + contentList.add(content); + + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + }*/ + + public int getPendingCount(String group) + { + // Select All Query + String countQuery = "SELECT * FROM " + TABLE_CONTENT + " WHERE "+KEY_DOWN+"=0 AND "+KEY_GROUP+"='"+group+"'"; + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.rawQuery(countQuery, null); + int value = cursor.getCount(); + cursor.close(); + db.close(); // Closing database connection + + // return count + return value; + } + + // Getting All content + public List getGroupContent(String group) { + List contentList = new ArrayList(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT+ " WHERE "+KEY_GROUP+"='"+group+"'"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + content.setGroup(cursor.getString(0)); + content.setUri(cursor.getString(1)); + content.setName(cursor.getString(2)); + content.setText(cursor.getString(3)); + content.setUrl(cursor.getString(4)); + content.setId(Integer.parseInt(cursor.getString(5))); + content.setTotal(Integer.parseInt(cursor.getString(6))); + content.setCrc(cursor.getString(7)); + // Adding content to list + contentList.add(content); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + // return contact list + return contentList; + } + + + // Getting All Contacts + public List getContent(String name, String group) { + List contentList = new ArrayList(); + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT+ " WHERE "+KEY_NAME+"='"+name+"' AND "+KEY_GROUP+"='"+group+"'"; + + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (cursor.moveToFirst()) { + do { + Content content = new Content(); + content.setGroup(cursor.getString(0)); + content.setUri(cursor.getString(1)); + content.setName(cursor.getString(2)); + content.setText(cursor.getString(3)); + content.setUrl(cursor.getString(4)); + content.setId(Integer.parseInt(cursor.getString(5))); + content.setTotal(Integer.parseInt(cursor.getString(6))); + content.setCrc(cursor.getString(7)); + // Adding content to list + contentList.add(content); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); // Closing database connection + return contentList; + } + + public int getReceived(String name,String group) + { + // Select All Query + String selectQuery = "SELECT * FROM " + TABLE_CONTENT+ " WHERE "+KEY_NAME+"='"+name+"' AND "+KEY_GROUP+"='"+group+"' AND "+KEY_DOWN+"=1"; + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + /*if (cursor.moveToFirst()) { + do { + Log.d("DB","Result "+cursor.getString(0)+" "+cursor.getString(4)); + } while (cursor.moveToNext()); + }*/ + int value = cursor.getCount(); + cursor.close(); + db.close(); // Closing database connection + + return value; + // ret + } + + // Getting contacts Count + public int getContentCount(String group) { + String countQuery = "SELECT * FROM " + TABLE_CONTENT +" WHERE "+KEY_GROUP+"='"+group+"'"; + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.rawQuery(countQuery, null); + int value = cursor.getCount(); + cursor.close(); + db.close(); // Closing database connection + + // return count + return value; + } + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/data/User.java b/demo-filesharing/src/main/java/network/datahop/localsharing/data/User.java new file mode 100644 index 0000000..68eac6f --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/data/User.java @@ -0,0 +1,60 @@ +package network.datahop.localsharing.data; + + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Created by stefanodp91 on 16/01/17. + */ + +public class User { + + private Long last; + private String name; + private String address; + + + public Long getCreatedOn() { + return last; + } + + public void setTimestamp(Long createdOn) { + this.last = createdOn; + } + + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object object) + { + boolean isEqual= false; + + if (object != null && object instanceof User) + { + isEqual = (this.name.equals (((User) object).name)); + } + + return isEqual; + } + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/GroupActivity.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/GroupActivity.java new file mode 100644 index 0000000..ea47822 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/GroupActivity.java @@ -0,0 +1,546 @@ +package network.datahop.localsharing.ui; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.PowerManager; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.view.ContextThemeWrapper; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import java.io.File; +import java.util.List; + +import network.datahop.localfirst.LocalFirstSDK; +import network.datahop.localfirst.data.Content; +import network.datahop.localfirst.data.ContentDatabaseHandler; +import network.datahop.localfirst.data.DataSharingClient; +import network.datahop.localfirst.data.Group; +import network.datahop.localfirst.net.DataHopService; +import network.datahop.localfirst.net.ble.GattServerCallback; +import network.datahop.localsharing.App; +import network.datahop.localsharing.MainActivity; +import network.datahop.localsharing.R; +import network.datahop.localsharing.data.AddFile; +import network.datahop.localsharing.ui.fragments.detail.ContentAdapter; +import network.datahop.localsharing.ui.fragments.detail.DataViewHolder; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; + +/** + * Demonstrates the use of {@link RecyclerView} with a {@link LinearLayoutManager} and a + * {@link GridLayoutManager}. + */ +public class GroupActivity extends AppCompatActivity implements DataViewHolder.Callbacks { + + private static final String TAG = GroupActivity.class.getSimpleName(); + private static final String KEY_LAYOUT_MANAGER = "layoutManager"; + private static final int SPAN_COUNT = 2; + private static final int CHOOSE_FILE_REQUESTCODE = 42; + private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 2; + + private enum LayoutManagerType { + GRID_LAYOUT_MANAGER, + LINEAR_LAYOUT_MANAGER + } + + + protected LayoutManagerType mCurrentLayoutManagerType; + protected RecyclerView mRecyclerView; + protected ContentAdapter mAdapter; + protected RecyclerView.LayoutManager mLayoutManager; + protected String[] mDataset; + + ContentDatabaseHandler db; + + String group; + + BroadcastReceiver mBroadcastReceiver; + @Override + public void onResume() { + //setHasOptionsMenu(true); + refreshAdapter(); + super.onResume(); + LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mBroadcastReceiver,getIntentFilter()); + } + + @Override + public void onPause() { + super.onPause(); + LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(mBroadcastReceiver); + } + + /*@Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) {*/ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle extras = getIntent().getExtras(); + if (extras != null) { + group = extras.getString("group"); + // and get whatever type user account id is + } + + db = new ContentDatabaseHandler(this); + //mProgressDialog = new ProgressDialog(this); + initDataset(group); + //View rootView = inflater.inflate(R.layout.fragment_content, container, false); + //rootView.setTag(TAG); + setContentView(R.layout.fragment_content); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(group); + //getSupportActionBar().setHomeButtonEnabled(true); + + + FloatingActionButton fab = findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext()); + // builder.setTitle("This app needs location access"); + // builder.setMessage("Please grant location access"); + // builder.setPositiveButton(android.R.string.ok, null); + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_READ_EXTERNAL_STORAGE); + } + }); + builder.show(); + } + } + G.Log(TAG, "Open file"); + openFile("*/*"); + } + }); + + + // BEGIN_INCLUDE(initializeRecyclerView) + mRecyclerView = (RecyclerView) findViewById(R.id.items_list); + + // LinearLayoutManager is used here, this will layout the elements in a similar fashion + // to the way ListView would layout elements. The RecyclerView.LayoutManager defines how + // elements are laid out. + mLayoutManager = new LinearLayoutManager(this); + + mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; + + if (savedInstanceState != null) { + // Restore saved layout manager type. + mCurrentLayoutManagerType = (LayoutManagerType) savedInstanceState + .getSerializable(KEY_LAYOUT_MANAGER); + } + setRecyclerViewLayoutManager(mCurrentLayoutManagerType); + + // Set CustomAdapter as the adapter for RecyclerView. + mRecyclerView.setAdapter(mAdapter); + + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + G.Log(TAG,"Broadcast received "+intent); + Bundle extras; + + switch (intent.getAction()) { + case LocalFirstSDK.DIRECT_CONNECTION: + extras = intent.getExtras(); + if (extras != null) { + if (extras.containsKey("user")) { + if(((App)getApplication()).isForeground()) { + acceptConnection(extras.getString("user")); + } else { + acceptanceNotification(extras.getString("user")); + } + } + } + break; + case LocalFirstSDK.NEW_CHUNK_RECEIVED: + String extra = intent.getStringExtra("group"); + G.Log(TAG,"New chunk "+extra +" "+group); + if(extra.equals(group)) + refreshAdapter(); + break; + + } + } + }; + + } + + /** + * Set RecyclerView's LayoutManager to the one given. + * + * @param layoutManagerType Type of layout manager to switch to. + */ + public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) { + int scrollPosition = 0; + + // If a layout manager has already been set, get current scroll position. + if (mRecyclerView.getLayoutManager() != null) { + scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()) + .findFirstCompletelyVisibleItemPosition(); + } + + switch (layoutManagerType) { + case GRID_LAYOUT_MANAGER: + mLayoutManager = new GridLayoutManager(this, SPAN_COUNT); + mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER; + break; + case LINEAR_LAYOUT_MANAGER: + mLayoutManager = new LinearLayoutManager(this); + mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; + break; + default: + mLayoutManager = new LinearLayoutManager(this); + mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; + } + + mRecyclerView.setLayoutManager(mLayoutManager); + mRecyclerView.scrollToPosition(scrollPosition); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.group, menu); + return true; + } + + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + // Save currently selected layout manager. + savedInstanceState.putSerializable(KEY_LAYOUT_MANAGER, mCurrentLayoutManagerType); + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + exit(); + //return true; + //noinspection SimplifiableIfStatement + break; + case R.id.action_clear: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Remove content?"); + // Set up the buttons + builder.setMessage(R.string.action_delete); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteVideos(group); + refreshAdapter(); + //LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LocalFirstSDK.RESTART)); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + break; + + } + return super.onOptionsItemSelected(item); + + } + /** + * Generates Strings for RecyclerView's adapter. This data would usually come + * from a local content provider or remote server. + */ + private void initDataset(String group) { + mDataset = new String[0]; + /*for (int i = 0; i < DATASET_COUNT; i++) { + mDataset[i] = "This is element #" + i; + } + */ + File dir = getExternalFilesDir(Config.FOLDER); + File[] subFiles = dir.listFiles(); + + List content = db.getContentDownloaded(group); + + G.Log(TAG, "Files " + subFiles.length + " " + db.getContentCount(group) + " " + content.size()+" "+group); + //G.Log(TAG, "Getcontent " + db.getDatabaseName() + " " + db.getContentCount(group) + " " + db.getPendingCount(group)); + + for (File files : subFiles) { + G.Log(TAG, "File " + files.getAbsolutePath()); + } + + mAdapter = new ContentAdapter(content, this,this,group); + + + + } + + private void openFile(String minmeType) { + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(minmeType); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // special intent for Samsung file manager + Intent sIntent = new Intent("com.sec.android.app.myfiles.PICK_DATA"); + // if you want any file type, you can skip next line + sIntent.putExtra("CONTENT_TYPE", minmeType); + sIntent.addCategory(Intent.CATEGORY_DEFAULT); + + Intent chooserIntent; + if (getPackageManager().resolveActivity(sIntent, 0) != null) { + // it is device with samsung file manager + chooserIntent = Intent.createChooser(sIntent, "Open file"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{intent}); + } else { + chooserIntent = Intent.createChooser(intent, "Open file"); + } + + try { + startActivityForResult(chooserIntent, CHOOSE_FILE_REQUESTCODE); + } catch (android.content.ActivityNotFoundException ex) { + Toast.makeText(this, "No suitable File Manager was found.", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + + // The ACTION_OPEN_DOCUMENT intent was sent with the request code + // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the + // response to some other intent, and the code below shouldn't run at all. + + super.onActivityResult(requestCode, resultCode, resultData); + if (requestCode == CHOOSE_FILE_REQUESTCODE && resultCode == Activity.RESULT_OK) { + // The document selected by the user won't be returned in the intent. + // Instead, a URI to that document will be contained in the return intent + // provided to this method as a parameter. + // Pull that URI using resultData.getData(). + if (resultData != null) { + + Uri uri = resultData.getData(); + G.Log(TAG, "Uri: " + uri.toString() + " " + uri.getPath() + " " + uri.getEncodedPath()+" "+group); + + //saveFile(uri); + //showProgressDialog("Adding file"); + //MainActivity activity = (MainActivity)getActivity(); + //activity.stopServices(); + (new AddFile(this, this, group)).execute(uri); + + } + } + } + + + @Override + public void onBackPressed() { + exit(); + } + + public void refreshAdapter() + { + G.Log(TAG,"refresh "+db.getContentDownloaded(group).size()); + mAdapter.refreshAdapter(db.getContentDownloaded(group)); + } + + public void onNewContent() + { + refreshAdapter(); + //LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LocalFirstSDK.RESTART)); + } + + @Override + protected void onNewIntent(Intent intent) { + G.Log(TAG,"onNewIntent"); + super.onNewIntent(intent); + setIntent(intent);//must store the new intent unless getIntent() will return the old one + processExtraData(); + } + + private void processExtraData(){ + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + String content; + if (extras != null) { + content = extras.getString("new_content"); + G.Log(TAG,"received "+content); + refreshAdapter(); + // and get whatever type user account id is + } + //use the data received here + } + + private void exit() + { + Intent intent = new Intent(GroupActivity.this, MainActivity.class); + db.groupClearPending(group); + // intent.putExtra(ChatUI.BUNDLE_RECIPIENT, groupRecipient); + //intent.putExtra(BUNDLE_CHANNEL_TYPE, Message.GROUP_CHANNEL_TYPE); + //setResult(1); + startActivity(intent); + finish(); + } + + public void acceptanceNotification(final String msg){ + Intent intent = new Intent(this, MainActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // build notification + // the addAction re-use the same intent to keep the example short + Notification n = new Notification.Builder(this) + .setContentTitle("Accept connection?") + .setContentText("User "+msg+" is trying to create a group with you.") + .setSmallIcon(R.mipmap.ic_stat_looks) + .setContentIntent(pIntent) + .setAutoCancel(true) + .addAction(R.drawable.ic_icon, "Accept", pIntent) + .addAction(R.drawable.ic_icon, "Decline", pIntent).build(); + //.addAction(R.drawable.ic_icon, "See", pIntent).build(); + + + NotificationManager notificationManager = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + notificationManager.notify(0, n); + + // Turn on the screen for notification + PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); + boolean result= powerManager.isInteractive() || Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT_WATCH && powerManager.isScreenOn(); + + if (!result){ + @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK |PowerManager.ACQUIRE_CAUSES_WAKEUP |PowerManager.ON_AFTER_RELEASE,"MH24_SCREENLOCK"); + wl.acquire(10000); + @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl_cpu = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MH24_SCREENLOCK"); + wl_cpu.acquire(10000); + } + } + + private void acceptConnection(String user) + { + AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.myDialog)); + //AlertDialog.Builder builder = new AlertDialog.Builder(that); + builder.setTitle("Request received"); + // Set up the buttons + builder.setMessage("Do you want to share files with " + user + "?"); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String action = GattServerCallback.DIRECT_CONNECTION_ACCEPTED; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast); + + //StatsHandler st = new StatsHandler(getApplicationContext()); + /*Group group = new Group(); + //group.setGroupId(db.getGroupMaxId() + 1); + group.setName(st.getUserName() + "-" + user); + G.Log(TAG, "Add group " + System.currentTimeMillis()); + group.setTimestamp(System.currentTimeMillis()); + if (!db.getGroups().contains(group)) + db.addGroup(group); + else + Toast.makeText(getApplicationContext(), "Group already exists", Toast.LENGTH_SHORT).show(); + //refresh();*/ + SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE); + String defaultName = getResources().getString(R.string.user_name); + String name = sharedPref.getString(getString(R.string.user_name), defaultName); + if(!LocalFirstSDK.addGroup(getApplicationContext(),name + "-" + user)) + Toast.makeText(getApplicationContext(), "Group already exists", Toast.LENGTH_SHORT).show(); + exit(); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + String action = GattServerCallback.DIRECT_CONNECTION_REJECTED; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast); + } + }); + + builder.show(); + } + + private void deleteVideos(String group) + { + File dir = getExternalFilesDir(network.datahop.localfirst.utils.Config.FOLDER); + File[] subFiles = dir.listFiles(); + + List content = db.getGroupContent(group); + network.datahop.localfirst.utils.G.Log(TAG,"Files " +subFiles.length+" "+content.size()); + for (Content cn : content) { + // Writing Contacts to log + String name = cn.getName(); + String chunkName=name+"."+cn.getId(); + network.datahop.localfirst.utils.G.Log(TAG,"Name:"+ name+ " desc:" + cn.getText() + " url:"+cn.getUrl()); + if (subFiles != null) { + //G.Log("Files " +subFiles); + for (File file : subFiles) { + network.datahop.localfirst.utils.G.Log("Filename " + cn.getName() + " " +file.getAbsolutePath()+" "+file.getName()+" "+file.length()); + if (file.getName().equals(name)||file.getName().equals(chunkName)) file.delete(); + } + } + db.rmContent(cn.getUri(),group); + + } + /*String action = DataSharingClient.DOWNLOAD_COMPLETED;; + Intent broadcast = new Intent(action); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(broadcast);*/ + //setMainFragment(); + //startService(); + + } + + private IntentFilter getIntentFilter() + { + IntentFilter filter = new IntentFilter(); + //filter.addAction(DiscoveryService.DISCOVERED); + //filter.addAction(DiscoveryService.SAME_DISCOVERED); + //filter.addAction(DataSharingClient.DOWNLOAD_COMPLETED); + //filter.addAction(DataSharingClient.NEW_VIDEO_RECEIVED); + filter.addAction(DataSharingClient.NEW_CHUNK_RECEIVED); + //filter.addAction(CLEAR_VIDEOS); + filter.addAction(DataHopService.NOT_SUPPORTED); + filter.addAction(GattServerCallback.DIRECT_CONNECTION); + //filter.addAction(TransferFunds.FUNDS_RESULTS); + return filter; + } + + + +} + diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/UserActivity.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/UserActivity.java new file mode 100644 index 0000000..53bc871 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/UserActivity.java @@ -0,0 +1,209 @@ +package network.datahop.localsharing.ui; + + +import android.Manifest; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import network.datahop.localfirst.LocalFirstSDK; +import network.datahop.localsharing.MainActivity; +import network.datahop.localsharing.R; +import network.datahop.localsharing.utils.G; +import network.datahop.localsharing.utils.SettingsPreferences; + +import static java.lang.Thread.sleep; + +public class UserActivity extends AppCompatActivity implements View.OnClickListener{ + + public static final String TAG="UserActivity"; + + private TextView mUserTextView; + + private static final int PERMISSION_REQUEST_FINE_LOCATION = 1; + private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 2; + private static final int PERMISSION_WIFI_STATE = 3; + + // UserActivity that=this; + SettingsPreferences timers; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.fragment_login); + + mUserTextView = findViewById(R.id.user); + timers = new SettingsPreferences(getApplicationContext()); + findViewById(R.id.setuser).setOnClickListener(this); + + } + + @Override + public void onClick(View v) { + int i = v.getId(); + if (i == R.id.setuser) { + if(mUserTextView.getText().length()<4||mUserTextView.getText().length()>6){ + Toast.makeText(getApplicationContext(), "Invalid username. Should be between 4 and 6 characters .", Toast.LENGTH_LONG).show(); + } else { + + //LocalFirstSDK.addGroup(this,"TestGroup"); + //ContentDatabaseHandler db = new ContentDatabaseHandler(getApplicationContext()); + //db.addGroup(group); + requestForPermissions(); + } + //createAccount(mEmailField.getText().toString(), mPasswordField.getText().toString()); + } + } + + + private void requestForPermissions() + { + if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_FINE_LOCATION); + } + }); + builder.show(); + } + if (this.checkSelfPermission(Manifest.permission.CHANGE_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.CHANGE_WIFI_STATE}, PERMISSION_WIFI_STATE); + } + }); + builder.show(); + } + if (this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); + } + }); + builder.show(); + } + /*if (this.checkSelfPermission(Manifest.permission.CHANGE_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.CHANGE_WIFI_STATE}, PERMISSION_REQUEST_WIFI_CHANGE); + } + }); + builder.show(); + } + if (this.checkSelfPermission(Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + requestPermissions(new String[]{Manifest.permission.ACCESS_WIFI_STATE}, PERMISSION_REQUEST_WIFI_STATE); + } + }); + builder.show(); + }*/ + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + G.Log(TAG,"Permissions "+requestCode+" "+permissions+" "+grantResults); + switch (requestCode) { + case PERMISSION_REQUEST_FINE_LOCATION: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted, yay! Do the + // contacts-related task you need to do. + G.Log(TAG,"Location accepted"); + timers.setLocationPermission(true); + if(timers.getStoragePermission()) + { + //StatsHandler st = new StatsHandler(getApplicationContext()); + //st.setUserName(mUserTextView.getText().toString()); + G.Log(TAG,"Set username "+mUserTextView.getText().toString()); + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("pref",Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.putString(getString(R.string.user_name), mUserTextView.getText().toString()); + editor.commit(); + LocalFirstSDK.setUser(mUserTextView.getText().toString()); + //ProgressDialog pd = ProgressDialog.show(this, "Loading", "Setting user name"); + Intent intent; + //try{sleep(2000);}catch (Exception e){} + // G.Log(TAG,"sleep exception "+e); + intent = new Intent(UserActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + //pd.dismiss(); + startActivity(intent); + } + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + G.Log(TAG,"Location not accepted"); + } + break; + } + case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE: + + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted, yay! Do the + // contacts-related task you need to do. + G.Log(TAG,"Storage accepted"); + timers.setStoragePermission(true); + //new CreateWallet(getApplicationContext()).execute(); + if(timers.getLocationPermission()) + { + //StatsHandler st = new StatsHandler(getApplicationContext()); + //st.setUserName(mUserTextView.getText().toString()); + G.Log(TAG,"Set username "+mUserTextView.getText().toString()); + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("pref",Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.putString(getString(R.string.user_name), mUserTextView.getText().toString()); + editor.commit(); + LocalFirstSDK.setUser(mUserTextView.getText().toString()); + //ProgressDialog pd = ProgressDialog.show(this, "Loading", "Setting user name"); + Intent intent; + //try{sleep(2000);}catch (Exception e){} + // G.Log(TAG,"sleep exception "+e); + intent = new Intent(UserActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + //pd.dismiss(); + startActivity(intent); + } + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + G.Log(TAG,"Storage not accepted"); + + } + } + + // other 'case' lines to check for other + // permissions this app might request. + + } + + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/FragmentCallback.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/FragmentCallback.java new file mode 100644 index 0000000..886e031 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/FragmentCallback.java @@ -0,0 +1,6 @@ +package network.datahop.localsharing.ui.fragments; + +public interface FragmentCallback { + void done(); + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/GroupsListFragment.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/GroupsListFragment.java new file mode 100644 index 0000000..814a511 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/GroupsListFragment.java @@ -0,0 +1,146 @@ +package network.datahop.localsharing.ui.fragments; + +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import network.datahop.localfirst.data.Group; +import network.datahop.localsharing.R; +import network.datahop.localsharing.data.ContentDatabaseHandler; +import network.datahop.localsharing.ui.fragments.detail.ItemDecoration; + +import network.datahop.localsharing.ui.fragments.detail.GroupsListAdapter; +import network.datahop.localsharing.ui.fragments.detail.OnGroupClickListener; +import network.datahop.localsharing.utils.G; + +import java.util.List; + + +public class GroupsListFragment extends Fragment { + private static final String TAG = GroupsListFragment.class.getName(); + + //private GroupsSyncronizer chatGroupsSynchronizer; + private OnGroupClickListener onChatGroupClickListener; + + // contacts list recyclerview + private RecyclerView recyclerViewChatGroups; + private LinearLayoutManager lmRvChatGroups; + private GroupsListAdapter chatGroupsListAdapter; + + // no contacts layout + private RelativeLayout noChatGroupsLayout; + + public static Fragment newInstance() { + Fragment mFragment = new GroupsListFragment(); + return mFragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + //chatGroupsSynchronizer = ChatManager.getInstance().getGroupsSyncronizer(); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_chat_groups_list, container, false); + + // init RecyclerView + recyclerViewChatGroups = view.findViewById(R.id.chat_groups_list); + recyclerViewChatGroups.addItemDecoration(new ItemDecoration(getContext(), + DividerItemDecoration.VERTICAL, + getResources().getDrawable(R.drawable.decorator_activity_my_groups_list))); + lmRvChatGroups = new LinearLayoutManager(getActivity()); + recyclerViewChatGroups.setLayoutManager(lmRvChatGroups); + ContentDatabaseHandler db = new ContentDatabaseHandler(getContext()); + for(Group group : db.getGroups()) + { + G.Log(TAG,"Group "+group.getGroupId()+" "+group.getName()); + } + updateChatGroupsListAdapter(db.getGroups()); + //updateChatGroupsListAdapter(chatGroupsSynchronizer.getChatGroups()); + + // no contacts layout + //noChatGroupsLayout = view.findViewById(R.id.layout_no_groups); + //toggleNoContactsLayoutVisibility(chatGroupsListAdapter.getItemCount()); + + //chatGroupsSynchronizer.addGroupsListener(this); + //chatGroupsSynchronizer.connect(); + + return view; + } + + public void updateChatGroupsListAdapter(List list) { + if (chatGroupsListAdapter == null) { + // init RecyclerView adapter + chatGroupsListAdapter = new GroupsListAdapter(getActivity(), list); + if (getOnChatGroupClickListener() != null) + chatGroupsListAdapter.setOnGroupClickListener(getOnChatGroupClickListener()); + recyclerViewChatGroups.setAdapter(chatGroupsListAdapter); + } else { + chatGroupsListAdapter.setList(list); + chatGroupsListAdapter.notifyDataSetChanged(); + } + } + + // toggle the no contacts layout visibilty. + // if there are items show the list of item, otherwise show a placeholder layout + /*private void toggleNoContactsLayoutVisibility(int itemCount) { + if (itemCount > 0) { + // show the item list + recyclerViewChatGroups.setVisibility(View.VISIBLE); + noChatGroupsLayout.setVisibility(View.GONE); + } else { + // show the placeholder layout + recyclerViewChatGroups.setVisibility(View.GONE); + noChatGroupsLayout.setVisibility(View.VISIBLE); + } + }*/ + + public void setOnChatGroupClickListener(OnGroupClickListener onChatGroupClickListener) { + this.onChatGroupClickListener = onChatGroupClickListener; + } + + public OnGroupClickListener getOnChatGroupClickListener() { + return onChatGroupClickListener; + } + + public void onGroupAdded(Group chatGroup, RuntimeException e) { + if (e == null) { + chatGroupsListAdapter.notifyDataSetChanged(); + } else { + Log.e(TAG, "ChatGroupsListFragment.onGroupAdded: e == " + e.toString()); + } + } + + public void onGroupChanged(Group chatGroup, RuntimeException e) { + if (e == null) { + chatGroupsListAdapter.notifyDataSetChanged(); + } else { + Log.e(TAG, "ChatGroupsListFragment.onGroupChanged: e == " + e.toString()); + } + + } + + public void onGroupRemoved(RuntimeException e) { + if (e == null) { + chatGroupsListAdapter.notifyDataSetChanged(); + } else { + Log.e(TAG, "ChatGroupsListFragment.onGroupRemoved: e == " + e.toString()); + } + + } + + +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/ServiceFragment.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/ServiceFragment.java new file mode 100644 index 0000000..5371e00 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/ServiceFragment.java @@ -0,0 +1,187 @@ + +package network.datahop.localsharing.ui.fragments; + +import android.annotation.SuppressLint; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.grandcentrix.tray.AppPreferences; + +import network.datahop.localfirst.net.StatsHandler; +import network.datahop.localsharing.R; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; + +public class ServiceFragment extends Fragment { + + private static final String TAG="ServiceFragment"; + public static ServiceFragment newInstance() { + // Create fragment arguments here (if necessary) + return new ServiceFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + m_handler = new Handler(); + //m_handler.postDelayed(m_statusUpdateRunnable, 1000); + + + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + @SuppressLint("InflateParams") + View v = inflater.inflate(R.layout.fragment_service, null); + + + + m_wifi_status_view = v.findViewById(R.id.wifi_status_view); + // m_wifi_status_view.setVisibility(View.GONE); + + m_Status = v.findViewById(R.id.status); + m_hsSSID = v.findViewById(R.id.sd); + m_connections = v.findViewById(R.id.started); + m_users = v.findViewById(R.id.users); + m_failed = v.findViewById(R.id.failed); + + m_btStatusView = v.findViewById(R.id.bt_status_view); + m_btCtView = v.findViewById(R.id.btconnect); + m_btStView = v.findViewById(R.id.btstatus); + return v; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) + { + G.Log("ServiceFragment::onActivityCreated()"); + super.onActivityCreated(savedInstanceState); + //m_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + m_appPreferences = new AppPreferences(getContext()); // this Preference comes for free from the library + + } + + @Override + public void + onResume() { + G.Log("ServiceFragment::onResume()"); + super.onResume(); + m_handler.post(m_statsUpdateRunnable); + + } + + @Override + public void + onPause() { + super.onPause(); + G.Log("ServiceFragment::onPause()"); + + // m_handler.removeCallbacks(m_statusUpdateRunnable); + m_handler.removeCallbacks(m_statsUpdateRunnable); + //m_handler.removeCallbacks(m_retryConnectionToService); + } + + private class UpdateTask extends AsyncTask { + + @Override + protected StatsHandler + doInBackground(Void... voids) + { + try { + //App app = (App)getActivity().getApplication(); + StatsHandler st = new StatsHandler(getActivity()); + return st; + + } + catch (Exception e) { + G.Log(TAG,"Error communicating with status service (" + e.getMessage() + ")"); + return null; + } + + } + + @Override + protected void + onPostExecute(StatsHandler fs) + { + if (fs == null) { + // when failed, try after 0.5 seconds + m_handler.postDelayed(m_statsUpdateRunnable, Config.statusRefrestIfFailed); + } + else { + m_btStatusView.setVisibility(View.VISIBLE); + m_btStView.setText(fs.getBtStatus()); + m_btCtView.setText(String.valueOf(fs.getBtConnections())); + m_Status.setText(String.valueOf(fs.getWStatus())); + m_hsSSID.setText(fs.getHsSSID()); + m_users.setText(String.valueOf(fs.getHsClients())); + m_connections.setText(String.valueOf(fs.getConnections())); + m_failed.setText(String.valueOf(fs.getConnectionsFailed())); + m_wifi_status_view.setVisibility(View.VISIBLE); + + // refresh after 5 seconds + m_handler.postDelayed(m_statsUpdateRunnable, Config.statusRefresh); + } + } + } + + ////////////////////////////////////////////////////////////////////////////// + + + private ViewGroup m_wifi_status_view; + + private TextView m_Status; + private TextView m_hsSSID; + private TextView m_users; + private TextView m_connections; + private TextView m_failed; + + private ViewGroup m_btStatusView; + private TextView m_btStView; + private TextView m_btCtView; + + private Handler m_handler; + /*private Runnable m_statusUpdateRunnable = new Runnable() { + @Override + public void run() + { + new StatusUpdateTask().execute(); + } + };*/ + + private Runnable m_statsUpdateRunnable = new Runnable() { + @Override + public void run() + { + new UpdateTask().execute(); + } + }; + +// private SharedPreferences m_sharedPreferences; + private AppPreferences m_appPreferences; +// private Messenger m_serviceMessenger2 = null; + + // public static final String PREF_UBICDN_SERVICE_STATUS = "UBICDN_SERVICE_STATUS"; + public static final String PREF_SERVICE_SOURCE = "SERVICE_TYPE"; + + /*private AlertDialog dialogVpn = null; + + private static final int REQUEST_VPN = 1; + private static final int REQUEST_INVITE = 2; + private static final int REQUEST_LOGCAT = 3; + public static final int REQUEST_ROAMING = 4;*/ + + private int restarts=0; + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/SettingsFragment.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/SettingsFragment.java new file mode 100644 index 0000000..4359598 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/SettingsFragment.java @@ -0,0 +1,188 @@ +/******************************************************* + * Copyright (C) 2017-2018 DataHop Network Ltd + * + * This file is part of DataHop Network project. + * + * All rights reserved + *******************************************************/ + +package network.datahop.localsharing.ui.fragments; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; + +import network.datahop.localsharing.MainActivity; +import network.datahop.localsharing.R; +import network.datahop.localsharing.utils.G; +import network.datahop.localsharing.utils.SettingsPreferences; + +public class SettingsFragment extends Fragment { + + public static SettingsFragment newInstance() { + // Create fragment arguments here (if necessary) + return new SettingsFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + preferences = new SettingsPreferences(getContext()); + + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + @SuppressLint("InflateParams") + View v = inflater.inflate(R.layout.fragment_settings, null); + MainActivity activity = (MainActivity)getActivity(); + + isSource = v.findViewById(R.id.checkbox_source); + isSource.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //App app = (App)getActivity().getApplication(); + if (((CheckBox) v).isChecked()) { + preferences.setScanning(false); + activity.stopServices(); + activity.startService(); + // app.setScanning(true); + G.Log("Set source "+ preferences.isScanning()); + } else { + // app.setScanning(false); + preferences.setScanning(true); + activity.stopServices(); + activity.startService(); + G.Log("Set source "+ preferences.isScanning()); + + } + + + } + }); + + localSharing = v.findViewById(R.id.checkbox_sharing); + localSharing.setEnabled(false); + localSharing.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + + if (((CheckBox) v).isChecked()) { + preferences.setLocalGroupsSharing(true); + activity.stopServices(); + activity.startService(); + } else { + // app.setScanning(false); + preferences.setLocalGroupsSharing(false); + activity.stopServices(); + activity.startService(); + } + + } + }); + + btScanTime = v.findViewById(R.id.bt_scan_input); + btIdleFgTime = v.findViewById(R.id.bt_advfg_input); + btIdleBgTime = v.findViewById(R.id.bt_advbg_input); + + btScanTime.addTextChangedListener(new TextChangedListener(btScanTime) { + @Override + public void onTextChanged(EditText target, Editable s) { + if(!s.toString().equals("")) preferences.setBtScanTime(Long.parseLong(s.toString())); + } + }); + btIdleFgTime.addTextChangedListener(new TextChangedListener(btIdleFgTime) { + @Override + public void onTextChanged(EditText target, Editable s) { + if(!s.toString().equals("")) preferences.setBtIdleFgTime(Long.parseLong(s.toString())); + } + }); + btIdleBgTime.addTextChangedListener(new TextChangedListener(btIdleBgTime) { + @Override + public void onTextChanged(EditText target, Editable s) { + if(!s.toString().equals("")) preferences.setBtIdleBgTime(Long.parseLong(s.toString())); + } + }); + + return v; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) + { + G.Log("ServiceFragment::onActivityCreated()"); + super.onActivityCreated(savedInstanceState); + + } + + @Override + public void + onResume() { + G.Log("ServiceFragment::onResume()"); + super.onResume(); + isSource.setChecked(!preferences.isScanning()); + localSharing.setChecked(preferences.isOnlyLocalGroupsSharing()); + btScanTime.setText(Long.toString(preferences.getBtScanTime()), TextView.BufferType.EDITABLE); + btIdleFgTime.setText(Long.toString(preferences.getBtIdleFgTime()), TextView.BufferType.EDITABLE); + btIdleBgTime.setText(Long.toString(preferences.getBtIdleBgTime()), TextView.BufferType.EDITABLE); + + } + + @Override + public void + onPause() { + super.onPause(); + G.Log("ServiceFragment::onPause()"); + + } + + + ////////////////////////////////////////////////////////////////////////////// + + private CheckBox isSource; + private CheckBox localSharing; + + private EditText btScanTime; + private EditText btIdleFgTime; + private EditText btIdleBgTime; + + + public static final String PREF_SERVICE_SOURCE = "SERVICE_TYPE"; + + SettingsPreferences preferences; + + public abstract class TextChangedListener implements TextWatcher { + private T target; + + public TextChangedListener(T target) { + this.target = target; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + this.onTextChanged(target, s); + } + + public abstract void onTextChanged(T target, Editable s); + } +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/AbstractRecyclerAdapter.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/AbstractRecyclerAdapter.java new file mode 100644 index 0000000..9fc70d9 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/AbstractRecyclerAdapter.java @@ -0,0 +1,173 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.app.Activity; +import android.content.Context; +import androidx.recyclerview.widget.RecyclerView; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * Custom abstrac adapter. + * It requires just to create the viewholder and the methods "onBindViewHolder()" and "onCreateViewHolder". + *

+ * It works fine from RecyclerView v7:23.0.2 to v7:27.0.2 + *

+ * Created by stefanodp91 on 26/08/2015. + * + * @param Object class + * @param ViewHolder class + */ +public abstract class AbstractRecyclerAdapter + extends RecyclerView.Adapter { + private Context context; + private Activity activity; + private List items; + + public AbstractRecyclerAdapter(Context context, List items) { + this.context = context; + this.items = items; + } + + public AbstractRecyclerAdapter(Activity activity, List items) { + this.activity = activity; + this.items = items; + } + + public void setList(List mList) { + this.items = mList; + } + + + @Override + public abstract U onCreateViewHolder(ViewGroup parent, int viewType); + + @Override + public abstract void onBindViewHolder(U holder, final int position); + + /** + * Return the item in the selected position + * + * @param position the item's position + * @return + */ + public T getItem(int position) { + return items.get(position); + } + + @Override + public int getItemCount() { + if (items == null) + return 0; + + return items.size(); + } + + public Context getContext() { + return context; + } + + public Activity getActivity() { + return activity; + } + + /** + * @return the list of items + */ + public List getItems() { + List mList = new ArrayList<>(); + + if (items != null && items.size() != 0) { + mList = items; + } + + return mList; + } + + /** + * Remove the item into a specific position + * + * @param position the position + */ + public void remove(int position) { + if (items != null && items.size() > 0) { + items.remove(position); + notifyItemRemoved(position); + } + } + + /** + * add an item into at the top of the list + * + * @param item the item to add + */ + public void insertTop(T item) { + int position = 0; + if (item != null) { + items.add(position, item); + notifyItemInserted(position); + } + } + + /** + * add an item into at the bottom of the list + * + * @param item the item to add + */ + public void insertBottom(T item) { + if (items != null) { + int position = items.size(); + if (item != null) { + items.add(position, item); + notifyItemInserted(position); + } + } + } + + /** + * add an item into at a specific position + * + * @param item the item to add + */ + public void insert(T item, int position) { + if (items != null) { + if (item != null) { + items.add(position, item); + notifyItemInserted(position); + } + } + } + + /** + * Clear the list + */ + public void clear() { + if (items != null) { + if (items.size() > 0) { + items.clear(); + } + notifyDataSetChanged(); + } + } + + /** + * Update an item with a new value + * + * @param item the item + */ + public void update(T item) { + if (item != null) { + List list = getItems(); + + int itemPosition = list.indexOf(item); + + if (itemPosition >= 0 && itemPosition < list.size()) { + list.set(itemPosition, item); + } else { + list.add(item); + } + notifyDataSetChanged(); + } + } +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/CompletingViewHolder.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/CompletingViewHolder.java new file mode 100644 index 0000000..10f0c11 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/CompletingViewHolder.java @@ -0,0 +1,51 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.content.Context; + +import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.List; + +import network.datahop.localfirst.data.Content; +import network.datahop.localsharing.R; + +public class CompletingViewHolder extends RecyclerView.ViewHolder { + + private static final String TAG = "DataViewHolder"; + List ct; + final Context context; + ProgressBar bar; + private final TextView textView,completedView; + + + public CompletingViewHolder(View v, List content, Context ctx) { + super(v); + this.context = ctx; + ct = content; + textView = v.findViewById(R.id.itemVideoTitleView); + bar = v.findViewById(R.id.progressbar); + bar.setIndeterminate(false); + completedView = v.findViewById(R.id.itemAdditionalDetails); + } + + public TextView getTextView() { + return textView; + } + + public ProgressBar getBar(){return bar;} + + public TextView getCompleted() { return completedView;} + + public void setCompleted() { + completedView.setText("Processing file..."); + //bar.setProgress(0); + //bar.setIndeterminate(true); + Log.d("ViewHolder","set completed "+bar.getProgress()+" "+bar.isIndeterminate()); + + } + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ContentAdapter.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ContentAdapter.java new file mode 100644 index 0000000..b6e0bc8 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ContentAdapter.java @@ -0,0 +1,194 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import androidx.core.content.FileProvider; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import network.datahop.localfirst.data.Content; +import network.datahop.localfirst.data.ContentDatabaseHandler; +import network.datahop.localsharing.BuildConfig; +import network.datahop.localsharing.R; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; + +/** + * Provide views to RecyclerView with data from mDataSet. + */ +public class ContentAdapter extends RecyclerView.Adapter { + + private static final String TAG = "ContentAdapter"; + private List mDataSet; + private Context context; + + private static final int COMPLETED = 0; + private static final int UNCOMPLETED = 1; + + ContentDatabaseHandler db; + private final String group; + DataViewHolder.Callbacks listener; + + public ContentAdapter(List ct, Context context, DataViewHolder.Callbacks listener, String group) { + this.context = context; + mDataSet = new ArrayList<>(); + int downloadedChunks = 1; + db = new ContentDatabaseHandler(context); + mDataSet = filterData(ct); + this.group = group; + this.listener = listener; + setHasStableIds(true); + } + + /*public void setContent(List ct) { + mDataSet = ct; + }*/ + + private List filterData(List ct) + { + String last=""; + List dataSet = new ArrayList<>(); + if (ct.size() > 0) { + Collections.sort(ct, new Comparator() { + @Override + public int compare(final Content object1, final Content object2) { + return object1.getName().compareTo(object2.getName()); + } + }); + } + //G.Log(TAG,"new adapter "+ct.size()); + for(Content content : ct) + { + //G.Log(TAG,"Content "+content.getName()); + /*if(last.equals(content.getName()))downloadedChunks++; + if(downloadedChunks==content.getTotal()) + { + mDataSet.add(content); + downloadedChunks = 1; + }*/ + if(!last.equals(content.getName())) + { + dataSet.add(content); + } + last = content.getName(); + } + + return dataSet; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + // Create a new view. + View v; + + switch(viewType) { + case COMPLETED: + // Inflate the first view type + G.Log(TAG,"Layout completed"); + v = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.list_stream_item, viewGroup, false); + return new DataViewHolder(v,mDataSet,context,listener, group); + + case UNCOMPLETED: + // inflate the second view type + G.Log(TAG,"Layout"); + v = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.list_item_progress, viewGroup, false); + return new CompletingViewHolder(v,mDataSet,context); + } + + + G.Log(TAG,"Default Layout"); + v = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.list_stream_item, viewGroup, false); + + + return new DataViewHolder(v,mDataSet,context,listener,group); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder v, final int position) { + G.Log(TAG, "Element " + position + " set " + mDataSet.get(position).getText() + " " + mDataSet.get(position).getName() + " " + mDataSet.get(position).getUrl()); + + if(v instanceof DataViewHolder) { + DataViewHolder viewHolder = (DataViewHolder) v; + viewHolder.getTextView().setText(mDataSet.get(position).getName()); + viewHolder.showImage(mDataSet.get(position).getName()); + viewHolder.setOnItemClickListener(new DataViewHolder.ClickListener() { + @Override + public void onItemClick(int position, View v, Content ct) { + G.Log(TAG, "onItemClick position: " + position); + + File f = new File(context.getExternalFilesDir(Config.FOLDER) + "/" + ct.getName()); + Intent newIntent = new Intent(Intent.ACTION_VIEW); + Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", f); + newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + newIntent.setData(uri); + try { + context.startActivity(newIntent); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, "No handler for this type of file.", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onItemLongClick(int position, View v, Content ct) { + G.Log(TAG, "onItemLongClick = " + ct.getName() + " " + position); + } + }); + } else { + G.Log(TAG,"Received "+db.getReceived(mDataSet.get(position).getName(),group)+" "+mDataSet.get(position).getTotal()); + CompletingViewHolder viewHolder = (CompletingViewHolder) v; + viewHolder.getTextView().setText(mDataSet.get(position).getName()); + viewHolder.getBar().setProgress(db.getReceived(mDataSet.get(position).getName(),group)*100/(mDataSet.get(position).getTotal())); + viewHolder.getCompleted().setText(String.valueOf(db.getReceived(mDataSet.get(position).getName(),group))+"/"+String.valueOf(mDataSet.get(position).getTotal())); + + + if(db.getReceived(mDataSet.get(position).getName(),group) == mDataSet.get(position).getTotal()) + viewHolder.setCompleted(); + + } + /*try { + G.Log(TAG, "Imgfile " + imgFile.getAbsolutePath() + " " + imgFile.toURI() + " " + imgFile.getCanonicalPath()); + } catch (Exception e) { + }*/ + + } + + @Override + public int getItemCount() { + return mDataSet == null? 0: mDataSet.size(); + } + + + @Override + public int getItemViewType(int position) { + Content content = mDataSet.get(position); + G.Log(TAG,"Get item type "+db.getReceived(content.getName(),group)+" "+content.getTotal()+" "+db.isContentJoined(content.getName(),group)); + if(db.isContentJoined(content.getName(),group)) + return COMPLETED; + else + return UNCOMPLETED; + } + + public void refreshAdapter(List dataitems) { + G.Log(TAG,"Refresh"); + mDataSet.clear(); + mDataSet.addAll(filterData(dataitems)); + notifyDataSetChanged(); + //notifyItemChanged(0); + } + + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/DataViewHolder.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/DataViewHolder.java new file mode 100644 index 0000000..b25d9b6 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/DataViewHolder.java @@ -0,0 +1,198 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.ThumbnailUtils; +import android.provider.MediaStore; + +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.webkit.MimeTypeMap; +import android.widget.ImageView; +import android.widget.TextView; + + +import java.io.File; +import java.util.List; + +import network.datahop.localfirst.data.Content; +import network.datahop.localfirst.data.ContentDatabaseHandler; + +import network.datahop.localsharing.R; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; + + + +public class DataViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{ + + private static final String TAG = "DataViewHolder"; + private final TextView textView; + private final ImageView imageView,deleteImageView; + List ct; + final Context context; + + private final String group; + private static DataViewHolder.ClickListener clickListener; + + public interface Callbacks { + void onNewContent(); + } + + public interface ClickListener { + void onItemClick(int position, View v, Content ct); + void onItemLongClick(int position, View v, Content ct); + } + + public DataViewHolder(View v, List content, Context ctx,Callbacks listener, String group) { + super(v); + this.group = group; + this.context = ctx; + ct=content; + v.setOnClickListener(this); + v.setOnLongClickListener(this); + textView = v.findViewById(R.id.itemVideoTitleView); + imageView = v.findViewById(R.id.itemThumbnailView); + deleteImageView = v.findViewById(R.id.itemDelete); + + // G.Log(TAG,"Adapter "+getAdapterPosition()); + // + //showImage(imgFile,this); + + deleteImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + G.Log(TAG,"On click delete"); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle("Erase "+ct.get(getAdapterPosition()).getUri()+"?"); + + // Set up the buttons + builder.setMessage(R.string.action_delete_item); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteItem(ct.get(getAdapterPosition()).getUri(),context); + listener.onNewContent(); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + } + }); + } + + public TextView getTextView() { + return textView; + } + + private ImageView getImageView() { + return imageView; + } + + @Override + public void onClick(View v) { + clickListener.onItemClick(getAdapterPosition(), v,ct.get(getAdapterPosition())); + } + + @Override + public boolean onLongClick(View v) { + clickListener.onItemLongClick(getAdapterPosition(), v,ct.get(getAdapterPosition())); + return false; + } + + + public void setOnItemClickListener(DataViewHolder.ClickListener clickListener) { + this.clickListener = clickListener; + } + + public void showImage(String name){ + + String yourFilePath = context.getExternalFilesDir(Config.FOLDER)+ "/" + name; + + File imgFile = new File(yourFilePath); + MimeTypeMap myMime = MimeTypeMap.getSingleton(); + String mimeType = myMime.getMimeTypeFromExtension(fileExt(imgFile.getAbsolutePath())); + G.Log(TAG,"Type "+mimeType); + + Bitmap bMap; + if(mimeType!=null) { + if (mimeType.contains("video")) { + bMap = ThumbnailUtils.createVideoThumbnail(imgFile.getAbsolutePath(), MediaStore.Images.Thumbnails.MINI_KIND); + this.getImageView().setImageBitmap(bMap); + } else if (mimeType.contains("image")) { + //bMap = ThumbnailUtils.createVideoThumbnail(imgFile.getAbsolutePath(), MediaStore.Images.Thumbnails.MINI_KIND); + //this.getImageView().setImageBitmap(bMap); + bMap = BitmapFactory.decodeFile(imgFile.getAbsolutePath()); + this.getImageView().setImageBitmap(bMap); + this.getImageView().setRotation(0); + } else if (mimeType.contains("word")) + this.getImageView().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_word)); + else if (mimeType.contains("power") || mimeType.contains("presentation")) + this.getImageView().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_powerpoint)); + else if (mimeType.contains("excel") || mimeType.contains("spreadsheet")) + this.getImageView().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_excel)); + else if (mimeType.contains("pdf")) + this.getImageView().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_pdf)); + else + this.getImageView().setImageDrawable(context.getResources().getDrawable(R.drawable.dummy_thumbnail)); + } else { + this.getImageView().setImageDrawable(context.getResources().getDrawable(R.drawable.dummy_thumbnail)); + } + } + + + private String fileExt(String url) { + if (url.indexOf("?") > -1) { + url = url.substring(0, url.indexOf("?")); + } + if (url.lastIndexOf(".") == -1) { + return null; + } else { + String ext = url.substring(url.lastIndexOf(".") + 1); + if (ext.indexOf("%") > -1) { + ext = ext.substring(0, ext.indexOf("%")); + } + if (ext.indexOf("/") > -1) { + ext = ext.substring(0, ext.indexOf("/")); + } + return ext.toLowerCase(); + + } + } + + private void deleteItem(String fname, Context context){ + G.Log(TAG,"Delete "+fname+" "+group); + File dir = context.getExternalFilesDir(Config.FOLDER); + File[] subFiles = dir.listFiles(); + ContentDatabaseHandler db = new ContentDatabaseHandler(context); + List content = db.getContent(fname,group); + //G.Log(TAG,"Files " +subFiles.length+" "+content.size()); + for (Content cn : content) { + // Writing Contacts to log + String name = fname; + if(cn.getId()>0)name+="."+cn.getId(); + //G.Log(TAG,"Name:"+ name+ " desc:" + cn.getText() + " url:"+cn.getUrl()); + if (subFiles != null) { + //G.Log("Files " +subFiles); + for (File file : subFiles) { + // G.Log("Filename " + cn.getName() + " " +file.getAbsolutePath()+" "+file.getName()+" "+file.length()); + if (file.getName().equals(name)||file.getName().equals(fname)) file.delete(); + } + } + db.rmContent(cn.getUri(),group); + + } + //Intent broadcast = new Intent(CLEAR_VIDEOS); + //LocalBroadcastManager.getInstance(context).sendBroadcast(broadcast); + } + +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/GroupsListAdapter.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/GroupsListAdapter.java new file mode 100644 index 0000000..aca4abb --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/GroupsListAdapter.java @@ -0,0 +1,167 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.content.Context; +import android.graphics.Typeface; +import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.amulyakhare.textdrawable.TextDrawable; +import com.amulyakhare.textdrawable.util.ColorGenerator; + +import network.datahop.localfirst.data.Group; +import network.datahop.localsharing.R; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by stefano on 29/06/2017. + */ +public class GroupsListAdapter extends AbstractRecyclerAdapter { + + private OnGroupClickListener onGroupClickListener; + + public OnGroupClickListener getOnGroupClickListener() { + return onGroupClickListener; + } + + public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) { + this.onGroupClickListener = onGroupClickListener; + } + + public GroupsListAdapter(Context context, List mList) { + super(context, mList); + setList(mList); + } + + private void sortItems(List mList) { + // sort by descending timestamp (first the last created, than the oldest) + Collections.sort(mList, new Comparator() { + @Override + public int compare(Group item1, Group item2) { + return Long.compare(item2.getCreatedOn(), item1.getCreatedOn()); + } + }); + } + + @Override + public void setList(List mList) { + sortItems(mList); + super.setList(mList); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.row_group, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + Group chatGroup = getItem(position); + + setName(holder, chatGroup.getName(),chatGroup.pendingItems()); + +// setCreatedOn(holder, chatGroup.getCreatedOnLong()); + + setImage(holder, chatGroup.getName()); + + setMembers(holder, chatGroup); + + setOnGroupClickListener(holder, chatGroup, position, getOnGroupClickListener()); + } + + + private void setName(ViewHolder holder, String group, int pending) { + + holder.name.setText(group); + Log.d("Viewholder",group+" pending "+pending); + if(pending>0) { + holder.name.setTypeface(holder.name.getTypeface(), Typeface.BOLD_ITALIC); + holder.pending.setVisibility(View.VISIBLE); + holder.pending.setText(String.valueOf(pending)); + } else {holder.pending.setVisibility(View.INVISIBLE);} + + + } + +// private void setCreatedOn(MyGroupsListAdapter.ViewHolder holder, long createdOn) { +// +// // parse the timestamp in a nice formal +// String timestampStr = TimeUtils.getFormattedTimestamp(createdOn); +// +// // set it +// holder.createdOn.setText(timestampStr); +// } + + private void setImage(ViewHolder holder, String groupName) { + /*Glide.with(holder.itemView.getContext()) + .load(imageUrl) + .placeholder(R.drawable.ic_group_avatar) + .bitmapTransform(new CropCircleTransformation(holder.itemView.getContext())) + .into(holder.image);*/ + + ColorGenerator generator = ColorGenerator.MATERIAL; // or use DEFAULT + + TextDrawable drawable = TextDrawable.builder() + .buildRound(groupName.substring(0,1).toUpperCase(), generator.getColor(groupName)); + holder.image.setImageDrawable(drawable); + } + + private void setMembers(ViewHolder holder, Group chatGroup) { + + String members; + if (chatGroup.getMembersList() != null && chatGroup.getMembersList().size() > 0) { + members = chatGroup.printMembersListWithSeparator(", "); + } else { + // if there are no members show the logged user as "you" + members = holder.itemView.getContext().getString(R.string.activity_message_list_group_info_you_label); + } + + //holder.members.setText(members); + } + + private void setOnGroupClickListener( + ViewHolder holder, + final Group chatGroup, + final int position, + final OnGroupClickListener callback) { + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + callback.onGroupClicked(chatGroup, position); + } + }); + holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + callback.onGroupLongClicked(chatGroup, position); + return true; + } + }); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + private final TextView name; + // private final TextView createdOn; + private final ImageView image; + private final TextView members; + private final TextView pending; + + public ViewHolder(View itemView) { + super(itemView); + name = (TextView) itemView.findViewById(R.id.recipient_display_name); +// createdOn = (TextView) itemView.findViewById(R.id.created_on); + image = (ImageView) itemView.findViewById(R.id.recipient_picture); + members = (TextView) itemView.findViewById(R.id.members); + pending = (TextView) itemView.findViewById(R.id.pending); + } + } +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ItemDecoration.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ItemDecoration.java new file mode 100644 index 0000000..5c25fbc --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/ItemDecoration.java @@ -0,0 +1,59 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.util.DisplayMetrics; +import android.view.View; + +/** + * Created by stefanodp91 on 27/02/18. + */ + +public class ItemDecoration extends DividerItemDecoration { + + private Context mContext; + private Drawable mDivider; + private final Rect mBounds = new Rect(); + + /** + * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a + * {@link LinearLayoutManager}. + * + * @param context Current context, it will be used to access resources. + * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}. + */ + public ItemDecoration(Context context, int orientation, Drawable divider) { + super(context, orientation); + mContext = context; + mDivider = divider; + } + + @Override + public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + canvas.save(); + final int leftWithMargin = convertDpToPx(56); + final int right = parent.getWidth(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + parent.getDecoratedBoundsWithMargins(child, mBounds); + final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child)); + final int top = bottom - mDivider.getIntrinsicHeight(); + mDivider.setBounds(leftWithMargin, top, right, bottom); + mDivider.draw(canvas); + } + canvas.restore(); + } + + private int convertDpToPx(int dp) { + return Math.round(dp * + (mContext.getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT)); + } +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnGroupClickListener.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnGroupClickListener.java new file mode 100644 index 0000000..6e1ebba --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnGroupClickListener.java @@ -0,0 +1,13 @@ +package network.datahop.localsharing.ui.fragments.detail; + + +import network.datahop.localfirst.data.Group; + +/** + * Created by stefanodp91 on 07/12/17. + */ + +public interface OnGroupClickListener { + void onGroupClicked(Group chatGroup, int position); + void onGroupLongClicked(Group chatGroup,int position); +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnTaskCompleted.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnTaskCompleted.java new file mode 100644 index 0000000..8994b33 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnTaskCompleted.java @@ -0,0 +1,5 @@ +package network.datahop.localsharing.ui.fragments.detail; + +public interface OnTaskCompleted{ + void onTaskCompleted(); +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnUserClickListener.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnUserClickListener.java new file mode 100644 index 0000000..06cb66d --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/OnUserClickListener.java @@ -0,0 +1,12 @@ +package network.datahop.localsharing.ui.fragments.detail; + + +import network.datahop.localsharing.data.User; + +/** + * Created by stefanodp91 on 07/12/17. + */ + +public interface OnUserClickListener { + void onUserClicked(User chatGroup, int position); +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/UsersListAdapter.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/UsersListAdapter.java new file mode 100644 index 0000000..003d22b --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/fragments/detail/UsersListAdapter.java @@ -0,0 +1,144 @@ +package network.datahop.localsharing.ui.fragments.detail; + +import android.content.Context; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.amulyakhare.textdrawable.TextDrawable; +import com.amulyakhare.textdrawable.util.ColorGenerator; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import network.datahop.localsharing.R; +import network.datahop.localsharing.data.User; + +/** + * Created by stefano on 29/06/2017. + */ +public class UsersListAdapter extends AbstractRecyclerAdapter { + + private OnUserClickListener onUserClickListener; + + public OnUserClickListener getOnUserClickListener() { + return onUserClickListener; + } + + public void setOnUserClickListener(OnUserClickListener onUserClickListener) { + this.onUserClickListener = onUserClickListener; + } + + public UsersListAdapter(Context context, List mList) { + super(context, mList); + setList(mList); + } + + private void sortItems(List mList) { + // sort by descending timestamp (first the last created, than the oldest) + Collections.sort(mList, new Comparator() { + @Override + public int compare(User item1, User item2) { + return Long.compare(item2.getCreatedOn(), item1.getCreatedOn()); + } + }); + } + + @Override + public void setList(List mList) { + sortItems(mList); + super.setList(mList); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.row_group, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + User chatGroup = getItem(position); + holder.pending.setVisibility(View.INVISIBLE); + + setName(holder, chatGroup.getName()); + +// setCreatedOn(holder, chatGroup.getCreatedOnLong()); + + setImage(holder, chatGroup.getName()); + + setMembers(holder, chatGroup); + + setOnUserClickListener(holder, chatGroup, position, getOnUserClickListener()); + } + + + private void setName(ViewHolder holder, String name) { + holder.name.setText(name); + } + +// private void setCreatedOn(MyGroupsListAdapter.ViewHolder holder, long createdOn) { +// +// // parse the timestamp in a nice formal +// String timestampStr = TimeUtils.getFormattedTimestamp(createdOn); +// +// // set it +// holder.createdOn.setText(timestampStr); +// } + + private void setImage(ViewHolder holder, String groupName) { + /*Glide.with(holder.itemView.getContext()) + .load(imageUrl) + .placeholder(R.drawable.ic_group_avatar) + .bitmapTransform(new CropCircleTransformation(holder.itemView.getContext())) + .into(holder.image);*/ + + ColorGenerator generator = ColorGenerator.MATERIAL; // or use DEFAULT + + TextDrawable drawable = TextDrawable.builder() + .buildRound(groupName.substring(0,1).toUpperCase(), generator.getColor(groupName)); + holder.image.setImageDrawable(drawable); + } + + private void setMembers(ViewHolder holder, User chatGroup) { + + + holder.members.setText((new Date(chatGroup.getCreatedOn())).toString()); + } + + private void setOnUserClickListener( + ViewHolder holder, + final User chatGroup, + final int position, + final OnUserClickListener callback) { + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + callback.onUserClicked(chatGroup, position); + } + }); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + private final TextView name; + // private final TextView createdOn; + private final ImageView image; + private final TextView members; + private final TextView pending; + + public ViewHolder(View itemView) { + super(itemView); + name = (TextView) itemView.findViewById(R.id.recipient_display_name); +// createdOn = (TextView) itemView.findViewById(R.id.created_on); + image = (ImageView) itemView.findViewById(R.id.recipient_picture); + members = (TextView) itemView.findViewById(R.id.members); + pending = (TextView) itemView.findViewById(R.id.pending); + } + } +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/ui/splash/SplashActivity.java b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/splash/SplashActivity.java new file mode 100644 index 0000000..333669d --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/ui/splash/SplashActivity.java @@ -0,0 +1,76 @@ +package network.datahop.localsharing.ui.splash; + +/** + * Created by srenevic on 08/09/16. + */ +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import network.datahop.localsharing.MainActivity; +import network.datahop.localsharing.R; +import network.datahop.localsharing.ui.UserActivity; +import network.datahop.localsharing.utils.Config; +import network.datahop.localsharing.utils.G; +import network.datahop.localsharing.utils.SettingsPreferences; + +public class SplashActivity extends AppCompatActivity { + + public static final String TAG="SplashActivity"; + + + SettingsPreferences timers; + @Override + protected void onCreate(Bundle savedInstanceState) { + + timers = new SettingsPreferences(getApplicationContext()); + + super.onCreate(savedInstanceState); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { + setContentView(R.layout.splash_activity); + } else { + setContentView(R.layout.splash_activity_legacy); + } + //StatsHandler st = new StatsHandler(getApplicationContext()); + //final String name = st.getUserName(); + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("pref",Context.MODE_PRIVATE); + String defaultName = getResources().getString(R.string.user_name); + String name = sharedPref.getString(getString(R.string.user_name), defaultName); + + G.Log(TAG,"User name "+name+" "+timers.getLocationPermission()+" "+timers.getStoragePermission()); + Thread thread = new Thread() { + @Override + public void run() { + try { + int waited = 0; + while (waited < Config.SPLASHTIMEOUT) { + sleep(1000); + waited += 1000; + } + Intent intent; + if(name.equals("user_name")||!timers.getLocationPermission()||!timers.getStoragePermission()) { + intent = new Intent(SplashActivity.this, UserActivity.class); + } else { + intent = new Intent(SplashActivity.this, MainActivity.class); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(intent); + SplashActivity.this.finish(); + } catch (InterruptedException e) { + + } finally { + SplashActivity.this.finish(); + } + + } + }; + thread.start(); + + } + + +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/utils/Config.java b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/Config.java new file mode 100755 index 0000000..93c164b --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/Config.java @@ -0,0 +1,69 @@ +/******************************************************* + * Copyright (C) 2017-2018 DataHop Network Ltd + * + * This file is part of DataHop Network project. + * + * All rights reserved + *******************************************************/ + + +package network.datahop.localsharing.utils; + +public class Config +{ + + public static final String meetingId ="DataHop"; + // How long to scan for Bluetooth LE devices. + public static final long bleScanDuration =2000; + + // How long to advertise for Bluetooth LE devices in foreground. + public static final long bleAdvertiseForegroundDuration = 15000; + + // How long to advertise for Bluetooth LE devices in background. + public static final long bleAdvertiseBackgroundDuration = 25000; + + // How long to advertise for Bluetooth LE devices in background. + public static final long statusRefresh = 5000; + + // How long to advertise for Bluetooth LE devices in background. + public static final long statusRefrestIfFailed = 500; + + //How long to wait for a connection to be successful + public static final long wifiConnectionWaitingTime = 30000; + + //How long to wait for another source device + public static final long networkWaitingTime = 2000; + + + public static final long hotspotRestartTime = 3600000; + + //Tries to create a face if failed because network not ready + public static final int maxRetry = 5; + + + public static final String SSID="ubicdnap"; + + public static final String passwd="Raspberry"; + + public static final String owner_ip="10.0.2.2"; + + public static final String ip="192.168.49"; + + public static final String port="8080"; + + public static final int MAX_CHUNK_SIZE=6000000; + + public static final int SPLASHTIMEOUT=5000; + + public static final String FOLDER="datahop"; + + public static final int HTTP_TIMEOUT=3000; + + + + + + + + +} // Config diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/utils/G.java b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/G.java new file mode 100644 index 0000000..93a2eb9 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/G.java @@ -0,0 +1,102 @@ +/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2015 Regents of the University of California + * + * This file is part of NFD (Named Data Networking Forwarding Daemon) Android. + * See AUTHORS.md for complete list of NFD Android authors and contributors. + * + * NFD Android is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * NFD Android is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * NFD Android, e.g., in COPYING.md file. If not, see . + */ + +package network.datahop.localsharing.utils; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Global class used for logging + * + */ +public class G { + + /** Flag that turns on/off debugging log output. */ + private static final boolean DEBUG = true; + + private static final boolean DEBUG_WIFI = false; + + private static final boolean DEBUG_BT = false; + + private static final List btTags = new ArrayList<>(Arrays.asList("BtPort","BluetoothAdapter", "BluetoothLeScanner","BluetoothLeAdvertiser","BLEServiceDiscovery","BLEAdvertisement","BleScanner","BTTransport","BtServiceDiscovery","BtServer","BtSwitcherDumb")); + + private static final List wifiTags = new ArrayList<>(Arrays.asList("ContentAdvertisement","WifiLink", "WifiDirectHotSpot")); + + /** Tag used in log output to identify NFD Service. */ + private static final String TAG = "DataHop"; + + /** + * Designated log message method that provides flexibility in message logging. + * + * @param tag Tag to identify log message. + * @param format Format qualifiers as used in String.format() + * @param args Output log message. + */ + public static void Log(String tag, String format, Object... args) { + if (DEBUG_WIFI) { + if(isWifi(tag)) + Log.d(tag, String.format(format, args)); + } else if (DEBUG_BT) { + if(isBt(tag)) + Log.d(tag, String.format(format, args)); + } else if(DEBUG){ + Log.d(tag, String.format(format, args)); + } + } + + /** + * Convenience method to log a message with a specified tag. + * + * @param tag Tag to identify log message. + * @param message Output log message. + */ + public static void Log(String tag, String message) { + Log(tag, "%s", message); + } + + /** + * Convenience method to log messages with the default tag. + * + * @param message Output log message. + */ + public static void Log(String message) { + Log(TAG, message); + } + + /** + * Gets the tag in which logs are posted with. + * + * @return TAG that is used by this log class. + */ + public static String getLogTag() { + return TAG; + } + + private static boolean isBt(String TAG){ + return btTags.contains(TAG); + } + + private static boolean isWifi(String TAG){ + return wifiTags.contains(TAG); + } +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/utils/PathUtil.java b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/PathUtil.java new file mode 100644 index 0000000..8e1019b --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/PathUtil.java @@ -0,0 +1,93 @@ +package network.datahop.localsharing.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +import java.net.URISyntaxException; + + +public class PathUtil { + /* + * Gets the file path of the given Uri. + */ + @SuppressLint("NewApi") + public static String getPath(Context context, Uri uri) throws URISyntaxException { + final boolean needToCheckUri = Build.VERSION.SDK_INT >= 19; + String selection = null; + String[] selectionArgs = null; + // Uri is different in versions after KITKAT (Android 4.4), we need to + // deal with different Uris. + if (needToCheckUri && DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) { + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + uri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + } else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + if ("image".equals(type)) { + uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + selection = "_id=?"; + selectionArgs = new String[]{split[1]}; + } + } + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = {MediaStore.Images.Media.DATA}; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + if (cursor.moveToFirst()) { + return cursor.getString(column_index); + } + } catch (Exception e) { + } + } else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + +} \ No newline at end of file diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/utils/SettingsPreferences.java b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/SettingsPreferences.java new file mode 100644 index 0000000..1db94f4 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/SettingsPreferences.java @@ -0,0 +1,231 @@ +package network.datahop.localsharing.utils; + +import android.content.Context; + +import net.grandcentrix.tray.AppPreferences; + +//import net.grandcentrix.tray.AppPreferences; + + +/** + * Created by srenevic on 01/01/18. + */ + +public class SettingsPreferences { + + private final static String WIFI="WF_Waiting_time"; + private final static String WIFI_HS_RESTART="WF_Hotspot_restart_time"; + private final static String PEER_SUCCESS="Peer_success_time"; + private final static String PEER_FAILED="Peer_retry_time"; + private final static String SD_SUCCESS="Sd_success_time"; + private final static String SD_FAILED="Sd_retry_time"; + private final static String MEETING_ID="meeting_Id"; + private final static String BT_SCAN="bt_scan_time"; + private final static String BT_FG="bt_idle_fg"; + private final static String BT_BG="bt_idle_bg"; + public final static String BT_ACT="bt_activated"; + public final static String WD_ACT="wd_activated"; + public final static String WALLET="wallet"; + public final static String SCAN ="source"; + public final static String ADDRESS="addr"; + public final static String BALANCE="balance"; + public final static String PRESTIGE="prestige"; + public final static String LOCALGROUPS="groups"; + + + public final static String LOC_PERM="location_permission"; + public final static String STOR_PERM="storage_permission"; + + final AppPreferences appPreferences; + + + public SettingsPreferences(Context context){ + //Getting if is source device checkbox enabled from sharedpreferences + appPreferences = new AppPreferences(context); // this Preference comes for free from the library + } + + + public long getWifiWaitingTime() + { + return appPreferences.getLong(WIFI,Config.wifiConnectionWaitingTime); + + } + + public void setWifiWaitingTime(long time) + { + appPreferences.put(WIFI,time); + } + + + public long getBtScanTime() + { + return appPreferences.getLong(BT_SCAN,Config.bleScanDuration); + } + + public void setBtScanTime(long time) + { + appPreferences.put(BT_SCAN,time); + + } + public long getBtIdleFgTime() + { + return appPreferences.getLong(BT_FG,Config.bleAdvertiseForegroundDuration); + } + + public void setBtIdleFgTime(long time) + { + appPreferences.put(BT_FG,time); + + } + public long getBtIdleBgTime() + { + return appPreferences.getLong(BT_BG,Config.bleAdvertiseBackgroundDuration); + } + + public void setBtIdleBgTime(long time) + { + appPreferences.put(BT_BG,time); + + } + + public long getHotspotRestartTime() + { + return appPreferences.getLong(WIFI_HS_RESTART,Config.hotspotRestartTime); + } + + public void setHotspotRestartTime(long time) + { + appPreferences.put(WIFI_HS_RESTART,time); + + } + + public String getMeetingId() + { + return appPreferences.getString(MEETING_ID,Config.meetingId); + } + + public void setMeetingId(String meetingId) + { + appPreferences.put(MEETING_ID,meetingId); + + } + + + public void setWd(boolean active) + { + + appPreferences.put(WD_ACT,active); + } + + public boolean getWd() + { + return appPreferences.getBoolean(WD_ACT,false); + } + + public void setBt(boolean active) + { + appPreferences.put(BT_ACT,active); + + } + + public boolean getBt() + { + return appPreferences.getBoolean(BT_ACT,false); + } + + public void setAddress(String address){ + appPreferences.put(ADDRESS,address); + } + + public String getAddress() + { + G.Log("Preferences","Wallet file "+appPreferences.getString(ADDRESS,"")); + return appPreferences.getString(ADDRESS,""); + } + + public void setWallet(String walletName){ + appPreferences.put(WALLET,walletName); + } + + public String getWallet() + { + G.Log("Preferences","Wallet file "+appPreferences.getString(WALLET,"")); + return appPreferences.getString(WALLET,""); + } + + public void setBalance(int balance){ + appPreferences.put(BALANCE,balance); + } + + public int getBalance() + { + //G.Log("Preferences","Wallet file "+appPreferences.getString(WALLET,"")); + return appPreferences.getInt(BALANCE,0); + } + + public void setScanning(boolean source) + { + appPreferences.put(SCAN,source); + } + + public boolean isScanning() + { + return appPreferences.getBoolean(SCAN,true); + + } + + public void setPrestige(boolean source) + { + appPreferences.put(PRESTIGE,source); + } + + public boolean isPrestigeEnabled() + { + return appPreferences.getBoolean(PRESTIGE,true); + + } + + public void setLocalGroupsSharing(boolean source) + { + appPreferences.put(LOCALGROUPS,source); + } + + public boolean isOnlyLocalGroupsSharing() + { + return appPreferences.getBoolean(LOCALGROUPS,true); + + } + + public void setStoragePermission(boolean source) + { + appPreferences.put(STOR_PERM,source); + } + + public boolean getStoragePermission() + { + return appPreferences.getBoolean(STOR_PERM,false); + + } + + public void setLocationPermission(boolean source) + { + appPreferences.put(LOC_PERM,source); + } + + public boolean getLocationPermission() + { + return appPreferences.getBoolean(LOC_PERM,false); + + } + + /*public Credentials getCredentials(){ + return credentials; + } + + public void setCredentials(Credentials credentials) + { + this.credentials = credentials; + }*/ + + +} diff --git a/demo-filesharing/src/main/java/network/datahop/localsharing/utils/Util.java b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/Util.java new file mode 100644 index 0000000..682dda3 --- /dev/null +++ b/demo-filesharing/src/main/java/network/datahop/localsharing/utils/Util.java @@ -0,0 +1,146 @@ +package network.datahop.localsharing.utils; + +import android.content.Context; +import android.os.Build; + +import java.text.DateFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class Util { + + + public static String getDeviceName() { + String manufacturer = Build.MANUFACTURER; + String model = Build.MODEL; + if (model.toLowerCase().startsWith(manufacturer.toLowerCase())) { + return capitalize(model); + } else { + return capitalize(manufacturer) + " " + model; + } + } + + + + private static String capitalize(String s) { + if (s == null || s.length() == 0) { + return ""; + } + char first = s.charAt(0); + if (Character.isUpperCase(first)) { + return s; + } else { + return Character.toUpperCase(first) + s.substring(1); + } + } + + //Current Android version data + public static String currentVersion(){ + double release=Double.parseDouble(Build.VERSION.RELEASE.replaceAll("(\\d+[.]\\d+)(.*)","$1")); + String codeName="Unsupported";//below Jelly bean OR above Oreo + if(release>=4.1 && release<4.4)codeName="Jelly Bean"; + else if(release<5)codeName="Kit Kat"; + else if(release<6)codeName="Lollipop"; + else if(release<7)codeName="Marshmallow"; + else if(release<8)codeName="Nougat"; + else if(release<9)codeName="Oreo"; + return codeName+" v"+release+", API Level: "+Build.VERSION.SDK_INT; + } + + + + public static String localizeNumber(Context context, long number) { + NumberFormat nf = NumberFormat.getInstance(); + return nf.format(number); + } + + private static String formatDate(Context context, String date) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + Date datum = null; + try { + datum = formatter.parse(date); + } catch (ParseException e) { + e.printStackTrace(); + } + + + DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); + + return df.format(datum); + } + + /*public static String localizeDate(Context context, String date) { + Resources res = context.getResources(); + String dateString = res.getString(R.string.upload_date_text); + + String formattedDate = formatDate(context, date); + return String.format(dateString, formattedDate); + } + + public static String localizeViewCount(Context context, long viewCount) { + return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, localizeNumber(context, viewCount)); + } + + public static String localizeSubscribersCount(Context context, long subscriberCount) { + return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, localizeNumber(context, subscriberCount)); + } + + public static String localizeStreamCount(Context context, long streamCount) { + return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount)); + } + + public static String shortCount(Context context, long count) { + if (count >= 1000000000) { + return Long.toString(count / 1000000000) + context.getString(R.string.short_billion); + } else if (count >= 1000000) { + return Long.toString(count / 1000000) + context.getString(R.string.short_million); + } else if (count >= 1000) { + return Long.toString(count / 1000) + context.getString(R.string.short_thousand); + } else { + return Long.toString(count); + } + } + + public static String shortViewCount(Context context, long viewCount) { + return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount)); + } + + public static String shortSubscriberCount(Context context, long subscriberCount) { + return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, shortCount(context, subscriberCount)); + } + + private static String getQuantity(Context context, @PluralsRes int pluralId, @StringRes int zeroCaseStringId, long count, String formattedCount) { + if (count == 0) return context.getString(zeroCaseStringId); + + // As we use the already formatted count, is not the responsibility of this method handle long numbers + // (it probably will fall in the "other" category, or some language have some specific rule... then we have to change it) + int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count; + return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); + } + + public static String getDurationString(long duration) { + if (duration < 0) { + duration = 0; + } + String output; + long days = duration / (24 * 60 * 60L); // greater than a day + duration %= (24 * 60 * 60L); + long hours = duration / (60 * 60L); // greater than an hour + duration %= (60 * 60L); + long minutes = duration / 60L; + long seconds = duration % 60L; + + //handle days + if (days > 0) { + output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds); + } else if (hours > 0) { + output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); + } else { + output = String.format(Locale.US, "%d:%02d", minutes, seconds); + } + return output; + }*/ + +} diff --git a/demo-filesharing/src/main/res/drawable-v24/dummy_thumbnail.png b/demo-filesharing/src/main/res/drawable-v24/dummy_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf3af72d523ce397a31033fc354a7d7b549b1cc GIT binary patch literal 5164 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 + + + + + + + + + diff --git a/demo-filesharing/src/main/res/drawable-v24/ic_excel.png b/demo-filesharing/src/main/res/drawable-v24/ic_excel.png new file mode 100644 index 0000000000000000000000000000000000000000..22c25dcba3eb11e91a402005a40458de9a5175a2 GIT binary patch literal 8602 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 + + diff --git a/demo-filesharing/src/main/res/drawable-v24/ic_icon_white.xml b/demo-filesharing/src/main/res/drawable-v24/ic_icon_white.xml new file mode 100644 index 0000000..5ea56a7 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable-v24/ic_icon_white.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/demo-filesharing/src/main/res/drawable-v24/ic_launcher_foreground.xml b/demo-filesharing/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/demo-filesharing/src/main/res/drawable-v24/ic_pdf.png b/demo-filesharing/src/main/res/drawable-v24/ic_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..aef5d296a9736aeb7e01e84f9a19efebd3d16d73 GIT binary patch literal 13649 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 OBDnHHr4mJ}kpBXW_otr# literal 0 HcmV?d00001 diff --git a/demo-filesharing/src/main/res/drawable-v24/ic_unknown.png b/demo-filesharing/src/main/res/drawable-v24/ic_unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..b19c121a34edb0f22ba49a370fda2f021a2de6d0 GIT binary patch literal 3420 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 OhM literal 0 HcmV?d00001 diff --git a/demo-filesharing/src/main/res/drawable/circle.xml b/demo-filesharing/src/main/res/drawable/circle.xml new file mode 100644 index 0000000..6e104a1 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/circle.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/drawable/decorator_activity_my_groups_list.xml b/demo-filesharing/src/main/res/drawable/decorator_activity_my_groups_list.xml new file mode 100644 index 0000000..d2a1106 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/decorator_activity_my_groups_list.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/drawable/ic_account.xml b/demo-filesharing/src/main/res/drawable/ic_account.xml new file mode 100644 index 0000000..b845dc3 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_account.xml @@ -0,0 +1,9 @@ + + + diff --git a/demo-filesharing/src/main/res/drawable/ic_launcher_background.xml b/demo-filesharing/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo-filesharing/src/main/res/drawable/ic_menu_camera.xml b/demo-filesharing/src/main/res/drawable/ic_menu_camera.xml new file mode 100644 index 0000000..634fe92 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_menu_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/demo-filesharing/src/main/res/drawable/ic_menu_gallery.xml b/demo-filesharing/src/main/res/drawable/ic_menu_gallery.xml new file mode 100644 index 0000000..03c7709 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_menu_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/demo-filesharing/src/main/res/drawable/ic_menu_manage.xml b/demo-filesharing/src/main/res/drawable/ic_menu_manage.xml new file mode 100644 index 0000000..aeb047d --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_menu_manage.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/drawable/ic_menu_send.xml b/demo-filesharing/src/main/res/drawable/ic_menu_send.xml new file mode 100644 index 0000000..fdf1c90 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_menu_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/demo-filesharing/src/main/res/drawable/ic_menu_share.xml b/demo-filesharing/src/main/res/drawable/ic_menu_share.xml new file mode 100644 index 0000000..338d95a --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_menu_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/demo-filesharing/src/main/res/drawable/ic_menu_slideshow.xml b/demo-filesharing/src/main/res/drawable/ic_menu_slideshow.xml new file mode 100644 index 0000000..5e9e163 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ + + + diff --git a/demo-filesharing/src/main/res/drawable/side_nav_bar.xml b/demo-filesharing/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 0000000..6d81870 --- /dev/null +++ b/demo-filesharing/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/layout/activity_main.xml b/demo-filesharing/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..f06be46 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/activity_main.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/demo-filesharing/src/main/res/layout/activity_main_legacy.xml b/demo-filesharing/src/main/res/layout/activity_main_legacy.xml new file mode 100644 index 0000000..434fd25 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/activity_main_legacy.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/demo-filesharing/src/main/res/layout/app_bar_main.xml b/demo-filesharing/src/main/res/layout/app_bar_main.xml new file mode 100644 index 0000000..232264c --- /dev/null +++ b/demo-filesharing/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/layout/circletext.xml b/demo-filesharing/src/main/res/layout/circletext.xml new file mode 100644 index 0000000..408f5a1 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/circletext.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/layout/content_main.xml b/demo-filesharing/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..1f542c2 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/content_main.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/layout/fragment_chat_groups_list.xml b/demo-filesharing/src/main/res/layout/fragment_chat_groups_list.xml new file mode 100644 index 0000000..1e926b1 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/fragment_chat_groups_list.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/layout/fragment_content.xml b/demo-filesharing/src/main/res/layout/fragment_content.xml new file mode 100644 index 0000000..92241c9 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/fragment_content.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo-filesharing/src/main/res/layout/fragment_login.xml b/demo-filesharing/src/main/res/layout/fragment_login.xml new file mode 100644 index 0000000..50f9e73 --- /dev/null +++ b/demo-filesharing/src/main/res/layout/fragment_login.xml @@ -0,0 +1,40 @@ + + + + + + + + +