├── .editorconfig ├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── app ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jim │ │ └── sharetocomputer │ │ ├── DownloadServiceTest.kt │ │ ├── Ext.kt │ │ ├── TestApplication.kt │ │ ├── TestRunner.kt │ │ ├── fastlane │ │ ├── MainScreenTest.kt │ │ ├── MyFileWritingScreenshotCallback.kt │ │ └── Screenshot.kt │ │ └── ui │ │ ├── receive │ │ └── ReceiveFragmentTest.kt │ │ ├── send │ │ └── SendFragmentTest.kt │ │ └── setting │ │ └── SettingFragmentTest.kt │ ├── debug │ └── AndroidManifest.xml │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── web │ │ │ ├── logo.png │ │ │ ├── main.html │ │ │ ├── receive.html │ │ │ ├── upload_failed_file_exist.html │ │ │ └── upload_success.html │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── jim │ │ │ └── sharetocomputer │ │ │ ├── ActionActivity.kt │ │ │ ├── AllOpen.kt │ │ │ ├── Application.kt │ │ │ ├── DownloadService.kt │ │ │ ├── Message.kt │ │ │ ├── Modules.kt │ │ │ ├── QrCodeInfo.kt │ │ │ ├── ShareInfo.kt │ │ │ ├── ShareRequest.kt │ │ │ ├── WebServerService.kt │ │ │ ├── WebUploadService.kt │ │ │ ├── coroutines │ │ │ └── TestableDispatchers.kt │ │ │ ├── ext │ │ │ └── ContextExt.kt │ │ │ ├── gateway │ │ │ ├── ActivityHelper.kt │ │ │ └── WifiApi.kt │ │ │ ├── logging │ │ │ ├── KoinLogger.kt │ │ │ ├── MyLog.kt │ │ │ └── MyUncaughtExceptionHandler.kt │ │ │ ├── ui │ │ │ ├── DummyActivity.kt │ │ │ ├── FragmentNavigation.kt │ │ │ ├── main │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ └── SectionsPagerAdapter.kt │ │ │ ├── receive │ │ │ │ ├── ReceiveFragment.kt │ │ │ │ ├── ReceiveNavigation.kt │ │ │ │ └── ReceiveViewModel.kt │ │ │ ├── send │ │ │ │ ├── SendFragment.kt │ │ │ │ └── SendViewModel.kt │ │ │ └── setting │ │ │ │ ├── AboutFragment.kt │ │ │ │ ├── SettingFragment.kt │ │ │ │ └── SettingNavigation.kt │ │ │ └── webserver │ │ │ ├── InputStreamNotifyWebServer.kt │ │ │ ├── WebServer.kt │ │ │ ├── WebServerMultipleFiles.kt │ │ │ ├── WebServerReceive.kt │ │ │ ├── WebServerSingleFile.kt │ │ │ └── WebServerText.kt │ └── res │ │ ├── drawable │ │ └── button_round_corner_primary.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_about.xml │ │ ├── fragment_main.xml │ │ ├── fragment_receive.xml │ │ └── fragment_send.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ └── nav_main.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── file_paths.xml │ │ └── setting.xml │ ├── sharedTest │ └── java │ │ └── com │ │ └── jim │ │ └── sharetocomputer │ │ ├── Helper.kt │ │ └── coroutines │ │ └── DirectDispatcher.kt │ └── test │ ├── java │ └── com │ │ └── jim │ │ └── sharetocomputer │ │ ├── ActionActivityTest.kt │ │ ├── ConnectionSetup.kt │ │ ├── ExtraMatchers.kt │ │ ├── MainActivityTest.kt │ │ ├── RobolectricApplication.kt │ │ ├── StopperThreadTest.kt │ │ ├── WebServerServiceTest.kt │ │ ├── ext │ │ └── ContextExtTest.kt │ │ ├── gateway │ │ ├── ActivityHelperTest.kt │ │ └── WifiApiTest.kt │ │ ├── ui │ │ ├── receive │ │ │ ├── ReceiveNavigationTest.kt │ │ │ └── ReceiveViewModelTest.kt │ │ ├── send │ │ │ └── SendViewModelTest.kt │ │ └── setting │ │ │ └── SettingNavigationTest.kt │ │ └── webserver │ │ ├── InputStreamNotifyWebServerTest.kt │ │ ├── WebServerMultipleFilesTest.kt │ │ ├── WebServerReceiveTest.kt │ │ ├── WebServerSingleFileTest.kt │ │ └── WebServerTextTest.kt │ └── resources │ └── robolectric.properties ├── build.gradle ├── fastlane ├── Fastfile ├── Screengrabfile └── metadata │ └── android │ ├── en-US │ ├── changelogs │ │ ├── 1100.txt │ │ ├── 1110.txt │ │ ├── 1120.txt │ │ ├── 1130.txt │ │ ├── 1140.txt │ │ └── 2000.txt │ ├── full_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ ├── screen_about.png │ │ │ ├── screen_main.png │ │ │ ├── screen_receive.png │ │ │ ├── screen_setting.png │ │ │ └── screen_sharing.png │ ├── short_description.txt │ ├── title.txt │ └── video.txt │ └── screenshots.html ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle/ 3 | local.properties 4 | .idea/ 5 | .DS_Store 6 | build/ 7 | captures/ 8 | app/release/ 9 | .externalNativeBuild/ 10 | fastlane/Appfile 11 | fastlane/report.xml 12 | fastlane/README.md -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | dist: trusty 3 | 4 | jdk: 5 | - oraclejdk8 6 | 7 | env: 8 | global: 9 | - ADB_INSTALL_TIMEOUT=8 10 | - ABI=x86_64 11 | - EMU_FLAVOR=default 12 | - ANDROID_HOME=/usr/local/android-sdk 13 | - TOOLS=${ANDROID_HOME}/tools 14 | - PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH} 15 | matrix: 16 | - API=25 17 | 18 | android: 19 | components: 20 | - tools 21 | 22 | licenses: 23 | - 'android-sdk-preview-license-.+' 24 | - 'android-sdk-license-.+' 25 | - 'google-gdk-license-.+' 26 | 27 | install: 28 | - echo 'count=0' > /home/travis/.android/repositories.cfg 29 | - echo y | sdkmanager "platform-tools" >/dev/null 30 | - echo y | sdkmanager "tools" >/dev/null 31 | - echo y | sdkmanager "build-tools;28.0.3" >/dev/null 32 | - echo y | sdkmanager "platforms;android-$API" >/dev/null 33 | - echo y | sdkmanager "platforms;android-28" >/dev/null 34 | - echo y | sdkmanager --channel=4 "emulator" >/dev/null 35 | - echo y | sdkmanager "extras;android;m2repository" >/dev/null 36 | - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI" >/dev/null 37 | - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M 38 | - emulator -verbose -avd test -no-accel -no-snapshot -no-window $AUDIO -camera-back none -camera-front none -selinux permissive -qemu -m 2048 & 39 | - android-wait-for-emulator 40 | 41 | script: 42 | - ./gradlew clean app:lint app:assembleDebug app:assembleDebugUnitTest app:assembleDebugAndroidTest 43 | - adb shell input keyevent 82 & 44 | - adb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp' 45 | - adb shell input touchscreen swipe 16 239 304 287 46 | - adb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp' 47 | - adb shell settings put global window_animation_scale 0 & 48 | - adb shell settings put global transition_animation_scale 0 & 49 | - adb shell settings put global animator_duration_scale 0 & 50 | - ./gradlew jacocoTestReport 51 | - ./gradlew app:assembleRelease 52 | 53 | after_success: 54 | - bash <(curl -s https://codecov.io/bash) 55 | 56 | after_failure: 57 | - adb logcat -d 58 | - adb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp' 59 | - adb shell "uiautomator dump /sdcard/view.xml; cat /sdcard/view.xml" 60 | 61 | before_cache: 62 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 63 | 64 | cache: 65 | directories: 66 | - $HOME/.gradle/caches/ 67 | - $HOME/.gradle/wrapper/ 68 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub](https://img.shields.io/github/license/jimmod/ShareToComputer.svg) 2 | [![Build Status](https://travis-ci.org/jimmod/ShareToComputer.svg?branch=master)](https://travis-ci.org/jimmod/ShareToComputer) 3 | [![codecov](https://codecov.io/gh/jimmod/ShareToComputer/branch/master/graph/badge.svg)](https://codecov.io/gh/jimmod/ShareToComputer) 4 | [![GitHub release](https://img.shields.io/github/release/jimmod/ShareToComputer.svg)](https://github.com/jimmod/ShareToComputer/releases/latest) 5 | 6 | # ShareToComputer 7 | 8 | Share any files from your phone to your computer & other phones through wifi 9 | 10 | [Get it on F-Droid](https://f-droid.org/packages/com.jim.sharetocomputer/) 13 | [Get it on Google Play](https://play.google.com/store/apps/details?id=com.jim.sharetocomputer) 16 | [Get it on Github](https://github.com/jimmod/ShareToComputer/releases/latest) 19 | 20 | 21 | 22 | # Description 23 | 24 | Easiest and fastest way to share text, images or any files from your phone to your computer. 25 | Require no installation on your computer. 26 | 27 | # How to use 28 | 29 | 1. Use file-manager, gallery or browser to select file that you want to share (long press and select share) 30 | 2. Choose Share to Computer 31 | 3. It will open the app and display an URL, e.g http://192.168.0.100:8080 32 | 4. Go to your computer browser and open the the URL 33 | 5. it will download/display the content you shared 34 | 35 | XDA thread: https://forum.xda-developers.com/android/apps-games/app-share-to-computer-t3949212 36 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'androidx.navigation.safeargs.kotlin' 6 | apply plugin: 'jacoco' 7 | apply plugin: "kotlin-allopen" 8 | 9 | android { 10 | compileSdkVersion 29 11 | defaultConfig { 12 | applicationId "com.jim.sharetocomputer" 13 | minSdkVersion 23 14 | targetSdkVersion 29 15 | versionCode 2000 16 | versionName "2.0.0" 17 | testInstrumentationRunner "com.jim.sharetocomputer.TestRunner" 18 | } 19 | 20 | dataBinding { 21 | enabled = true 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | debug { 30 | testCoverageEnabled true 31 | } 32 | } 33 | 34 | androidExtensions { 35 | experimental = true 36 | } 37 | 38 | testOptions { 39 | animationsDisabled true 40 | 41 | unitTests { 42 | includeAndroidResources = true 43 | } 44 | } 45 | 46 | lintOptions { 47 | textOutput("stdout") 48 | warningsAsErrors true 49 | abortOnError true 50 | disable 'MissingTranslation', 'GoogleAppIndexingWarning' 51 | } 52 | 53 | sourceSets { 54 | String sharedTestDir = 'src/sharedTest/java' 55 | test { 56 | java.srcDir sharedTestDir 57 | } 58 | androidTest { 59 | java.srcDir sharedTestDir 60 | } 61 | } 62 | 63 | kotlinOptions { 64 | jvmTarget = '1.8' 65 | } 66 | 67 | compileOptions { 68 | encoding = 'UTF-8' 69 | sourceCompatibility JavaVersion.VERSION_1_8 70 | targetCompatibility JavaVersion.VERSION_1_8 71 | } 72 | 73 | useLibrary 'android.test.runner' 74 | useLibrary 'android.test.base' 75 | useLibrary 'android.test.mock' 76 | } 77 | 78 | allOpen { 79 | annotation "com.jim.sharetocomputer.AllOpen" 80 | } 81 | 82 | configurations.all { 83 | resolutionStrategy.force "androidx.test:core:$testExtCore" 84 | } 85 | 86 | dependencies { 87 | // Kotlin 88 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" 89 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion" 90 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion" 91 | implementation "org.koin:koin-android:$koinVersion" 92 | implementation "org.koin:koin-android-viewmodel:$koinVersion" 93 | 94 | // Support 95 | implementation 'androidx.appcompat:appcompat:1.1.0' 96 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 97 | implementation 'androidx.core:core-ktx:1.2.0-beta01' 98 | implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" 99 | implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" 100 | implementation 'androidx.preference:preference:1.1.0' 101 | 102 | // Other 103 | implementation 'org.nanohttpd:nanohttpd:2.3.1' 104 | implementation 'com.google.code.gson:gson:2.8.5' 105 | implementation 'com.journeyapps:zxing-android-embedded:3.6.0' 106 | implementation 'com.elvishew:xlog:1.6.1' 107 | 108 | // Flavor 109 | debugImplementation 'androidx.fragment:fragment-testing:1.1.0' 110 | 111 | // Testing 112 | testImplementation 'junit:junit:4.12' 113 | //noinspection GradleDependency 114 | testImplementation "androidx.test:core:$testExtCore" 115 | testImplementation 'androidx.test.ext:junit:1.1.1' 116 | testImplementation 'org.robolectric:robolectric:4.3' 117 | testImplementation "org.koin:koin-test:$koinVersion" 118 | testImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" 119 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutinesVersion" 120 | testImplementation 'org.mockito:mockito-core:3.0.0' 121 | testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0' 122 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" 123 | androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" 124 | androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" 125 | androidTestImplementation 'tools.fastlane:screengrab:1.1.0' 126 | androidTestImplementation "androidx.test:runner:$testExtCore" 127 | androidTestImplementation 'org.mockito:mockito-android:3.0.0' 128 | androidTestImplementation("org.koin:koin-test:$koinVersion") { exclude group: 'org.mockito' } 129 | } 130 | 131 | jacoco { 132 | toolVersion = jacocoVersion 133 | } 134 | 135 | tasks.withType(Test) { 136 | jacoco.includeNoLocationClasses = true 137 | } 138 | 139 | task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) { 140 | 141 | reports { 142 | xml.enabled = true 143 | html.enabled = true 144 | } 145 | 146 | def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] 147 | def debugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter) 148 | def mainSrc = "$project.projectDir/src/main/java" 149 | 150 | sourceDirectories.setFrom files([mainSrc]) 151 | classDirectories.setFrom files([debugTree]) 152 | executionData.setFrom fileTree(dir: project.buildDir, includes: [ 153 | 'jacoco/*.exec', 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' 154 | ]) 155 | } 156 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/Ext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer 18 | 19 | import android.Manifest 20 | import androidx.test.rule.GrantPermissionRule 21 | 22 | internal fun permissionGrant() = GrantPermissionRule.grant( 23 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 24 | Manifest.permission.READ_EXTERNAL_STORAGE 25 | )!! -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/TestApplication.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer 2 | 3 | import tools.fastlane.screengrab.Screengrab 4 | import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy 5 | 6 | class TestApplication : Application() { 7 | 8 | override fun onCreate() { 9 | super.onCreate() 10 | Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy()) 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/TestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | 7 | class TestRunner : AndroidJUnitRunner() { 8 | 9 | override fun newApplication( 10 | cl: ClassLoader?, 11 | className: String?, 12 | context: Context? 13 | ): Application { 14 | return super.newApplication(cl, TestApplication::class.java.name, context) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/fastlane/MainScreenTest.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer.fastlane 2 | 3 | import android.app.Instrumentation 4 | import android.content.ClipData 5 | import android.content.Intent 6 | import android.net.Uri 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.test.espresso.Espresso.onView 9 | import androidx.test.espresso.action.ViewActions.click 10 | import androidx.test.espresso.assertion.ViewAssertions.matches 11 | import androidx.test.espresso.intent.Intents 12 | import androidx.test.espresso.intent.matcher.IntentMatchers 13 | import androidx.test.espresso.intent.rule.IntentsTestRule 14 | import androidx.test.espresso.matcher.ViewMatchers.* 15 | import com.jim.sharetocomputer.R 16 | import com.jim.sharetocomputer.permissionGrant 17 | import com.jim.sharetocomputer.ui.main.MainActivity 18 | import org.junit.Rule 19 | import org.junit.Test 20 | import java.io.File 21 | 22 | 23 | class MainScreenTest { 24 | 25 | @get:Rule 26 | val rule = IntentsTestRule(MainActivity::class.java) 27 | 28 | @get:Rule 29 | val grant = permissionGrant() 30 | 31 | @Test 32 | fun screen_send() { 33 | setupDummyImageSelect() 34 | assertMainScreenIsDisplayed() 35 | Screenshot.take("screen_main") 36 | 37 | clickShareImage() 38 | 39 | assertSharingScreenIsDisplayed() 40 | 41 | Screenshot.take("screen_sharing") 42 | 43 | try { 44 | clickStopShare() 45 | } catch (e: Exception) { 46 | //ignore for CI 47 | } 48 | } 49 | 50 | @Test 51 | fun screen_receive() { 52 | clickReceiveTab() 53 | 54 | assertReceiveScreenIsDisplayed() 55 | Screenshot.take("screen_receive") 56 | } 57 | 58 | @Test 59 | fun screen_setting() { 60 | clickSettingTab() 61 | 62 | assertSettingScreenIsDisplayed() 63 | Screenshot.take("screen_setting") 64 | 65 | clickAbout() 66 | assertAboutScreenIsDisplayed() 67 | Screenshot.take("screen_about") 68 | } 69 | 70 | private fun setupDummyImageSelect() { 71 | val resultIntent = Intent().apply { 72 | val item = ClipData.Item(uri) 73 | clipData = ClipData("", emptyArray(), item) 74 | } 75 | val result = Instrumentation.ActivityResult(AppCompatActivity.RESULT_OK, resultIntent) 76 | Intents.intending(IntentMatchers.hasAction(Intent.ACTION_PICK)).respondWith(result) 77 | } 78 | 79 | private fun clickShareImage() { 80 | onView(withId(R.id.share_media)).perform(click()) 81 | } 82 | 83 | private fun clickSettingTab() { 84 | onView(withText(R.string.tab_title_setting)).perform(click()) 85 | } 86 | 87 | private fun clickReceiveTab() { 88 | onView(withText(R.string.tab_title_receive)).perform(click()) 89 | } 90 | 91 | private fun clickAbout() { 92 | onView(withText(R.string.about)).perform(click()) 93 | } 94 | 95 | private fun assertAboutScreenIsDisplayed() { 96 | onView(withId(R.id.layout_about)).check(matches(isDisplayed())) 97 | } 98 | 99 | private fun assertMainScreenIsDisplayed() { 100 | onView(withText(R.string.share_image_video)).check(matches(isDisplayed())) 101 | } 102 | 103 | private fun assertSharingScreenIsDisplayed() { 104 | onView(withId(R.id.layout_sharing_send)).check(matches(isDisplayed())) 105 | } 106 | 107 | private fun assertSettingScreenIsDisplayed() { 108 | onView(withText(R.string.title_send_feedback_preference)).check(matches(isDisplayed())) 109 | } 110 | 111 | private fun assertReceiveScreenIsDisplayed() { 112 | onView(withText(R.string.receive_from_computer)).check(matches(isDisplayed())) 113 | } 114 | 115 | private fun clickStopShare() { 116 | onView(withId(R.id.stop_share)).perform(click()) 117 | } 118 | 119 | companion object { 120 | private val uri = Uri.fromFile(File.createTempFile("temp", "del"))!! 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/fastlane/MyFileWritingScreenshotCallback.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.fastlane 20 | 21 | import android.content.Context 22 | import tools.fastlane.screengrab.FileWritingScreenshotCallback 23 | import java.io.File 24 | 25 | class MyFileWritingScreenshotCallback(context: Context) : FileWritingScreenshotCallback(context) { 26 | 27 | override fun getScreenshotFile(screenshotDirectory: File, screenshotName: String): File { 28 | val screenshotFileName = "$screenshotName.png" 29 | return File(screenshotDirectory, screenshotFileName) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/fastlane/Screenshot.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.fastlane 20 | 21 | import androidx.test.platform.app.InstrumentationRegistry 22 | import tools.fastlane.screengrab.Screengrab 23 | 24 | object Screenshot { 25 | 26 | private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext } 27 | 28 | fun take(screen: String) { 29 | Screengrab.screenshot( 30 | screen, 31 | Screengrab.getDefaultScreenshotStrategy(), 32 | MyFileWritingScreenshotCallback(context) 33 | ) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/ui/receive/ReceiveFragmentTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.receive 20 | 21 | import androidx.fragment.app.testing.launchFragmentInContainer 22 | import androidx.test.espresso.Espresso.onView 23 | import androidx.test.espresso.action.ViewActions.click 24 | import androidx.test.espresso.matcher.ViewMatchers.withId 25 | import com.jim.sharetocomputer.R 26 | import org.junit.Before 27 | import org.junit.Test 28 | import org.koin.core.context.startKoin 29 | import org.koin.core.context.stopKoin 30 | import org.koin.dsl.module 31 | import org.mockito.Mock 32 | import org.mockito.Mockito 33 | import org.mockito.MockitoAnnotations 34 | 35 | class ReceiveFragmentTest { 36 | 37 | @Mock 38 | lateinit var viewModel: ReceiveViewModel 39 | 40 | private val testModule = module { 41 | single { viewModel } 42 | } 43 | 44 | @Before 45 | fun before() { 46 | MockitoAnnotations.initMocks(this) 47 | stopKoin() 48 | startKoin { modules(testModule) } 49 | 50 | } 51 | 52 | @Test 53 | fun click_scan_qr_code() { 54 | launchFragmentInContainer() 55 | 56 | onView(withId(R.id.scan_qr_code)).perform(click()) 57 | 58 | Mockito.verify(viewModel).scanQrCode() 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/ui/send/SendFragmentTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.send 20 | 21 | import android.app.Application 22 | import android.graphics.drawable.Drawable 23 | import androidx.fragment.app.testing.launchFragmentInContainer 24 | import androidx.lifecycle.MutableLiveData 25 | import androidx.test.core.app.ApplicationProvider 26 | import androidx.test.espresso.Espresso.onView 27 | import androidx.test.espresso.ViewInteraction 28 | import androidx.test.espresso.action.ViewActions 29 | import androidx.test.espresso.assertion.ViewAssertions.matches 30 | import androidx.test.espresso.matcher.ViewMatchers 31 | import androidx.test.espresso.matcher.ViewMatchers.withId 32 | import androidx.test.espresso.matcher.ViewMatchers.withText 33 | import androidx.test.internal.runner.junit4.statement.UiThreadStatement 34 | import com.jim.sharetocomputer.R 35 | import org.hamcrest.Matchers.not 36 | import org.junit.Before 37 | import org.junit.Test 38 | import org.koin.core.context.startKoin 39 | import org.koin.core.context.stopKoin 40 | import org.koin.dsl.module 41 | import org.mockito.Mock 42 | import org.mockito.Mockito 43 | import org.mockito.MockitoAnnotations 44 | 45 | 46 | class SendFragmentTest { 47 | 48 | @Mock 49 | lateinit var sendViewModel: SendViewModel 50 | 51 | private val testModule = module { 52 | single { sendViewModel } 53 | } 54 | 55 | private val application by lazy { ApplicationProvider.getApplicationContext() } 56 | private val isSharing = MutableLiveData() 57 | private val deviceIp = MutableLiveData() 58 | private val devicePort = MutableLiveData() 59 | private val qrCodeDrawable = MutableLiveData() 60 | private val isAbleToShare = MutableLiveData() 61 | private val ip = "1.1.1.1" 62 | private val port = 1111 63 | private val address = "http://1.1.1.1:1111" 64 | 65 | @Before 66 | fun before() { 67 | MockitoAnnotations.initMocks(this) 68 | stopKoin() 69 | startKoin { modules(testModule) } 70 | 71 | Mockito.doReturn(isSharing).`when`(sendViewModel).isSharing() 72 | Mockito.doReturn(deviceIp).`when`(sendViewModel).deviceIp() 73 | Mockito.doReturn(devicePort).`when`(sendViewModel).devicePort() 74 | Mockito.doReturn(qrCodeDrawable).`when`(sendViewModel).qrCode() 75 | Mockito.doReturn(isAbleToShare).`when`(sendViewModel).isAbleToShare() 76 | UiThreadStatement.runOnUiThread { 77 | isSharing.value = false 78 | isAbleToShare.value = true 79 | } 80 | } 81 | 82 | @Test 83 | fun ui_displayed_no_share() { 84 | 85 | launchFragment() 86 | 87 | shareMediaButton().isDisplayed() 88 | shareFileButton().isDisplayed() 89 | stopShareButton().isNotDisplayed() 90 | qrCode().isNotDisplayed() 91 | url().isNotDisplayed() 92 | } 93 | 94 | @Test 95 | fun ui_displayed_share_is_on_going() { 96 | UiThreadStatement.runOnUiThread { 97 | isSharing.value = true 98 | deviceIp.value = ip 99 | devicePort.value = port 100 | qrCodeDrawable.value = application.getDrawable(R.drawable.abc_ab_share_pack_mtrl_alpha) 101 | } 102 | 103 | launchFragment() 104 | 105 | shareMediaButton().isNotDisplayed() 106 | shareFileButton().isNotDisplayed() 107 | stopShareButton().isDisplayed() 108 | qrCode().isDisplayed() 109 | url().withText(address) 110 | } 111 | 112 | @Test 113 | fun share_media_button_click() { 114 | launchFragment() 115 | shareMediaButton().click() 116 | 117 | Mockito.verify(sendViewModel).selectMedia() 118 | } 119 | 120 | @Test 121 | fun share_file_button_click() { 122 | launchFragment() 123 | shareFileButton().click() 124 | 125 | Mockito.verify(sendViewModel).selectFile() 126 | } 127 | 128 | @Test 129 | fun stop_share_button_click() { 130 | UiThreadStatement.runOnUiThread { 131 | isSharing.value = true 132 | } 133 | 134 | launchFragment() 135 | stopShareButton().click() 136 | 137 | Mockito.verify(sendViewModel).stopShare() 138 | } 139 | 140 | private fun shareMediaButton() = onView(withId(R.id.share_media)) 141 | private fun shareFileButton() = onView(withText(R.string.share_file)) 142 | private fun stopShareButton() = onView(withText(R.string.stop_share)) 143 | private fun qrCode() = onView(withId(R.id.qrcode)) 144 | private fun url() = onView(withId(R.id.url)) 145 | 146 | private fun launchFragment() { 147 | launchFragmentInContainer() 148 | } 149 | } 150 | 151 | private fun ViewInteraction.isDisplayed() { 152 | check(matches(ViewMatchers.isDisplayed())) 153 | } 154 | 155 | private fun ViewInteraction.isNotDisplayed() { 156 | check(matches(not(ViewMatchers.isDisplayed()))) 157 | } 158 | 159 | private fun ViewInteraction.withText(s: String) { 160 | check(matches(ViewMatchers.withText(s))) 161 | } 162 | 163 | private fun ViewInteraction.click() { 164 | perform(ViewActions.click()) 165 | } 166 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jim/sharetocomputer/ui/setting/SettingFragmentTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.setting 20 | 21 | import android.app.Instrumentation.ActivityResult 22 | import android.content.Intent 23 | import android.os.Environment 24 | import androidx.appcompat.app.AppCompatActivity 25 | import androidx.fragment.app.testing.launchFragmentInContainer 26 | import androidx.test.espresso.Espresso.onView 27 | import androidx.test.espresso.ViewInteraction 28 | import androidx.test.espresso.action.ViewActions 29 | import androidx.test.espresso.assertion.ViewAssertions.matches 30 | import androidx.test.espresso.intent.Intents 31 | import androidx.test.espresso.intent.matcher.IntentMatchers.* 32 | import androidx.test.espresso.matcher.ViewMatchers 33 | import androidx.test.espresso.matcher.ViewMatchers.withText 34 | import com.jim.sharetocomputer.Application 35 | import com.jim.sharetocomputer.R 36 | import com.jim.sharetocomputer.assertTimeout 37 | import com.jim.sharetocomputer.permissionGrant 38 | import org.hamcrest.Matchers 39 | import org.junit.* 40 | import org.koin.core.context.startKoin 41 | import org.koin.core.context.stopKoin 42 | import org.koin.dsl.module 43 | import org.koin.test.KoinTest 44 | import org.mockito.Mock 45 | import org.mockito.Mockito 46 | import org.mockito.MockitoAnnotations 47 | import java.io.File 48 | 49 | class SettingFragmentTest : KoinTest { 50 | 51 | @get:Rule 52 | val grant = permissionGrant() 53 | 54 | @Mock 55 | lateinit var navigation: SettingNavigation 56 | 57 | private val testModule = module { 58 | single { navigation } 59 | } 60 | 61 | @Before 62 | fun before() { 63 | MockitoAnnotations.initMocks(this) 64 | Intents.init() 65 | stopKoin() 66 | startKoin { modules(testModule) } 67 | } 68 | 69 | @After 70 | fun after() { 71 | Intents.release() 72 | } 73 | 74 | @Test 75 | fun all_setting_displayed() { 76 | launchFragment() 77 | 78 | downloadLog().isDisplayed() 79 | sendFeedback().isDisplayed() 80 | about().isDisplayed() 81 | } 82 | 83 | @Test 84 | fun send_feedback() { 85 | launchFragment() 86 | Intents.intending(anyIntent()) 87 | .respondWith(ActivityResult(AppCompatActivity.RESULT_OK, null)) 88 | 89 | sendFeedback().click() 90 | 91 | Intents.intended( 92 | Matchers.allOf( 93 | hasAction(Intent.ACTION_SEND), 94 | hasType("message/rfc822"), 95 | hasExtra(Intent.EXTRA_EMAIL, arrayOf(Application.EMAIL_ADDRESS)) 96 | ) 97 | ) 98 | } 99 | 100 | @Suppress("DEPRECATION") 101 | @Test 102 | fun download_log() { 103 | val file = File( 104 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), 105 | "mylog.log" 106 | ) 107 | file.delete() 108 | assertTimeout(2000) { 109 | Assert.assertFalse(file.exists()) 110 | } 111 | 112 | launchFragment() 113 | 114 | downloadLog().click() 115 | 116 | assertTimeout(2000) { 117 | Assert.assertTrue(file.exists()) 118 | } 119 | } 120 | 121 | @Test 122 | fun open_about() { 123 | launchFragment() 124 | 125 | about().click() 126 | 127 | Mockito.verify(navigation).openAboutScreen() 128 | } 129 | 130 | private fun launchFragment() { 131 | launchFragmentInContainer() 132 | } 133 | 134 | private fun downloadLog() = onView(withText(R.string.title_download_log_preference)) 135 | private fun sendFeedback() = onView(withText(R.string.title_send_feedback_preference)) 136 | private fun about() = onView(withText(R.string.title_about_preference)) 137 | 138 | private fun ViewInteraction.click() = perform(ViewActions.click()) 139 | private fun ViewInteraction.isDisplayed() = check(matches(ViewMatchers.isDisplayed())) 140 | 141 | } 142 | -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 57 | 58 | 59 | 60 | 63 | 66 | 67 | 68 | 73 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/assets/web/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimmod/ShareToComputer/711310e2ed86a388dc2a67c7947f6407eb86557b/app/src/main/assets/web/logo.png -------------------------------------------------------------------------------- /app/src/main/assets/web/receive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | [[title]] 4 | 63 | 64 | 65 |
66 |
67 |
68 |
69 | 70 | 71 |
72 |
73 | 74 | 75 |
76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimmod/ShareToComputer/711310e2ed86a388dc2a67c7947f6407eb86557b/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ActionActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.jim.sharetocomputer.logging.MyLog 8 | 9 | class ActionActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | val action = intent?.action 14 | MyLog.i("onCreate action: $action") 15 | if (action == ACTION_STOP_SHARE) { 16 | MyLog.i("*Stopping web server service") 17 | stopService(WebServerService.createIntent(this, null)) 18 | } else if (action == ACTION_STOP_DOWNLOAD) { 19 | MyLog.i("*Stopping download service") 20 | stopService(DownloadService.createIntent(this, null)) 21 | } 22 | finish() 23 | } 24 | 25 | companion object { 26 | const val ACTION_STOP_SHARE = "com.jim.sharetocomputer.STOP_SHARE" 27 | const val ACTION_STOP_DOWNLOAD = "com.jim.sharetocomputer.STOP_DOWNLOAD" 28 | 29 | fun stopShareIntent(context: Context) = Intent(context, ActionActivity::class.java).apply { 30 | action = ACTION_STOP_SHARE 31 | } 32 | 33 | fun stopDownloadIntent(context: Context) = 34 | Intent(context, ActionActivity::class.java).apply { 35 | action = ACTION_STOP_DOWNLOAD 36 | } 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/AllOpen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer 20 | 21 | annotation class AllOpen -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/Application.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer 18 | 19 | import com.jim.sharetocomputer.logging.KoinLogger 20 | import com.jim.sharetocomputer.logging.MyLog 21 | import com.jim.sharetocomputer.logging.MyUncaughtExceptionHandler 22 | import org.koin.android.ext.koin.androidContext 23 | import org.koin.core.KoinApplication 24 | import org.koin.core.context.startKoin 25 | 26 | 27 | open class Application : android.app.Application() { 28 | 29 | override fun onCreate() { 30 | super.onCreate() 31 | 32 | Thread.setDefaultUncaughtExceptionHandler( 33 | MyUncaughtExceptionHandler(Thread.getDefaultUncaughtExceptionHandler()) 34 | ) 35 | 36 | MyLog.setupLogging(this) 37 | MyLog.i("Application is starting") 38 | MyLog.i("*QR Code version: $QR_CODE_VERSION") 39 | 40 | startKoin { 41 | KoinApplication.logger = KoinLogger() 42 | androidContext(this@Application) 43 | modules(applicationModule) 44 | } 45 | } 46 | 47 | companion object { 48 | const val EMAIL_ADDRESS = "sharetocomputer@gmail.com" 49 | const val QR_CODE_VERSION = 2 50 | const val CHANNEL_ID = "DEFAULT_CHANNEL" 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/Message.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer 18 | 19 | object Message { 20 | internal const val ERROR_CONTENT_NOT_SET = "ERROR: No Content" 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/Modules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer 18 | 19 | import androidx.fragment.app.Fragment 20 | import com.jim.sharetocomputer.gateway.ActivityHelper 21 | import com.jim.sharetocomputer.gateway.WifiApi 22 | import com.jim.sharetocomputer.ui.main.MainViewModel 23 | import com.jim.sharetocomputer.ui.receive.ReceiveNavigation 24 | import com.jim.sharetocomputer.ui.receive.ReceiveViewModel 25 | import com.jim.sharetocomputer.ui.send.SendViewModel 26 | import com.jim.sharetocomputer.ui.setting.SettingNavigation 27 | import com.jim.sharetocomputer.webserver.WebServerMultipleFiles 28 | import com.jim.sharetocomputer.webserver.WebServerSingleFile 29 | import com.jim.sharetocomputer.webserver.WebServerText 30 | import org.koin.android.viewmodel.dsl.viewModel 31 | import org.koin.dsl.module 32 | 33 | 34 | val applicationModule = module { 35 | factory { (port: Int) -> WebServerText(port) } 36 | factory { (port: Int) -> WebServerSingleFile(get(), port) } 37 | factory { (port: Int) -> WebServerMultipleFiles(get(), port) } 38 | factory { (fragment: Fragment) -> SettingNavigation(fragment) } 39 | factory { WifiApi(get()) } 40 | factory { ActivityHelper() } 41 | viewModel { SendViewModel(get(), get(), get()) } 42 | viewModel { MainViewModel(get()) } 43 | viewModel { (fragment: Fragment) -> 44 | ReceiveViewModel( 45 | get(), 46 | get(), 47 | ReceiveNavigation(fragment, get()) 48 | ) 49 | } 50 | } 51 | 52 | object Module { 53 | const val PORT = "PORT" 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/QrCodeInfo.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class QrCodeInfo( 6 | @SerializedName("version") 7 | val version: Int, 8 | 9 | @SerializedName("url") 10 | val url: String 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ShareInfo.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ShareInfo( 6 | @SerializedName("total") 7 | val total: Int, 8 | 9 | @SerializedName("files") 10 | val files: List 11 | ) 12 | 13 | data class FileInfo( 14 | @SerializedName("filename") 15 | val filename: String 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ShareRequest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer 18 | 19 | import android.net.Uri 20 | import android.os.Parcelable 21 | import kotlinx.android.parcel.Parcelize 22 | 23 | sealed class ShareRequest : Parcelable { 24 | 25 | @Parcelize 26 | data class ShareRequestText( 27 | val text: String 28 | ) : ShareRequest() 29 | 30 | @Parcelize 31 | data class ShareRequestSingleFile( 32 | val uri: Uri 33 | ) : ShareRequest() 34 | 35 | @Parcelize 36 | data class ShareRequestMultipleFile( 37 | val uris: List 38 | ) : ShareRequest() 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/WebUploadService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | //TODO check Android 10 compatibility (NetworkInfo is deprecated) 20 | @file:Suppress("DEPRECATION") 21 | 22 | package com.jim.sharetocomputer 23 | 24 | import android.app.* 25 | import android.content.BroadcastReceiver 26 | import android.content.Context 27 | import android.content.Intent 28 | import android.content.IntentFilter 29 | import android.net.NetworkInfo 30 | import android.net.wifi.WifiManager 31 | import android.os.Build 32 | import android.os.IBinder 33 | import androidx.core.app.NotificationCompat 34 | import androidx.core.app.NotificationManagerCompat 35 | import androidx.lifecycle.MutableLiveData 36 | import com.jim.sharetocomputer.ext.getIp 37 | import com.jim.sharetocomputer.logging.MyLog 38 | import com.jim.sharetocomputer.webserver.WebServerReceive 39 | import java.io.IOException 40 | import java.net.ServerSocket 41 | 42 | class WebUploadService : Service() { 43 | 44 | 45 | private var webServer: WebServerReceive? = null 46 | private val wifiListener = object : BroadcastReceiver() { 47 | override fun onReceive(context: Context?, intent: Intent?) { 48 | val info = intent?.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) 49 | if (info == null || !info.isConnected) { 50 | MyLog.i("Stopping service because Wi-Fi disconnected") 51 | stopSelf() 52 | } 53 | } 54 | } 55 | 56 | override fun onCreate() { 57 | super.onCreate() 58 | isRunning.value = true 59 | val filter = IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION) 60 | registerReceiver(wifiListener, filter) 61 | } 62 | 63 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 64 | MyLog.i("onStartCommand") 65 | startForeground(NOTIFICATION_ID, createNotification()) 66 | webServer?.stop() 67 | val port = findFreePort() 68 | WebUploadService.port.value = port 69 | webServer = WebServerReceive(this, port) 70 | webServer!!.start() 71 | startForeground(NOTIFICATION_ID, createNotification()) 72 | 73 | return START_STICKY 74 | } 75 | 76 | private fun findFreePort(): Int { 77 | for (port in 8080..8100) { 78 | var socket: ServerSocket? = null 79 | try { 80 | socket = ServerSocket(port) 81 | } catch (ex: IOException) { 82 | continue 83 | } finally { 84 | try { 85 | socket?.close() 86 | } catch (ex: IOException) { 87 | } 88 | } 89 | MyLog.i("free port: $port") 90 | return port 91 | } 92 | MyLog.e("no free port found") 93 | throw IOException("no free port found") 94 | } 95 | 96 | private fun createNotification(): Notification { 97 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 98 | val name = getString(R.string.channel_name) 99 | val descriptionText = getString(R.string.channel_description) 100 | val importance = NotificationManager.IMPORTANCE_DEFAULT 101 | val channel = NotificationChannel(Application.CHANNEL_ID, name, importance).apply { 102 | description = descriptionText 103 | } 104 | // Register the channel with the system 105 | val notificationManager = NotificationManagerCompat.from(this) 106 | notificationManager.createNotificationChannel(channel) 107 | } 108 | val stopIntent = ActionActivity.stopShareIntent(this) 109 | val stopPendingIntent = TaskStackBuilder.create(this).run { 110 | addNextIntentWithParentStack(stopIntent) 111 | getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) 112 | } 113 | val builder = NotificationCompat.Builder(this, Application.CHANNEL_ID) 114 | .setSmallIcon(R.mipmap.ic_launcher) 115 | .setContentTitle(getString(R.string.notification_title)) 116 | .setContentText( 117 | getString( 118 | R.string.notification_server_text, 119 | this.getIp(), 120 | WebUploadService.port.value.toString() 121 | ) 122 | ) 123 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) 124 | .addAction( 125 | R.mipmap.ic_launcher, getString(R.string.stop_share), 126 | stopPendingIntent 127 | ) 128 | return builder.build() 129 | } 130 | 131 | override fun onDestroy() { 132 | MyLog.i("onDestroy") 133 | unregisterReceiver(wifiListener) 134 | webServer?.stop() 135 | isRunning.value = false 136 | super.onDestroy() 137 | } 138 | 139 | override fun onBind(intent: Intent?): IBinder? { 140 | return null 141 | } 142 | 143 | companion object { 144 | private const val NOTIFICATION_ID = 1946 145 | 146 | var isRunning = MutableLiveData().apply { value = false } 147 | var port = MutableLiveData().apply { value = 8080 } 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/coroutines/TestableDispatchers.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer.coroutines 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | object TestableDispatchers { 8 | 9 | @JvmStatic 10 | val Main: CoroutineDispatcher 11 | get() = mainDispatcher 12 | 13 | @JvmStatic 14 | val Default: CoroutineDispatcher 15 | get() = defaultDispatcher 16 | 17 | @JvmStatic 18 | val IO: CoroutineDispatcher 19 | get() = ioDispatcher 20 | 21 | @JvmStatic 22 | val Unconfined: CoroutineDispatcher 23 | get() = unconfinedDispatcher 24 | 25 | private var mainDispatcher: CoroutineDispatcher = Dispatchers.Main 26 | private var defaultDispatcher: CoroutineDispatcher = Dispatchers.Default 27 | private var ioDispatcher: CoroutineDispatcher = Dispatchers.IO 28 | private var unconfinedDispatcher: CoroutineDispatcher = Dispatchers.Unconfined 29 | 30 | @VisibleForTesting 31 | fun setMain(dispatcher: CoroutineDispatcher) { 32 | mainDispatcher = dispatcher 33 | } 34 | 35 | @VisibleForTesting 36 | fun setDefault(dispatcher: CoroutineDispatcher) { 37 | defaultDispatcher = dispatcher 38 | } 39 | 40 | @VisibleForTesting 41 | fun setIo(dispatcher: CoroutineDispatcher) { 42 | ioDispatcher = dispatcher 43 | } 44 | 45 | @VisibleForTesting 46 | fun setUnconfined(dispatcher: CoroutineDispatcher) { 47 | unconfinedDispatcher = dispatcher 48 | } 49 | 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ext/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer.ext 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import android.net.ConnectivityManager 6 | import android.net.NetworkCapabilities 7 | import android.net.Uri 8 | import android.net.wifi.WifiManager 9 | import android.os.Build 10 | import android.provider.OpenableColumns 11 | import com.jim.sharetocomputer.R 12 | import com.jim.sharetocomputer.logging.MyLog 13 | 14 | 15 | internal fun Context.getAppName(): String = getString(R.string.app_name) 16 | 17 | internal fun Context.getFileName(uri: Uri): String { 18 | var result: String? = null 19 | if (uri.scheme == "content") { 20 | contentResolver.query(uri, null, null, null, null).use { cursor -> 21 | if (cursor != null && cursor.moveToFirst()) { 22 | result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) 23 | } 24 | } 25 | } 26 | if (result == null) { 27 | result = uri.path 28 | val cut = result!!.lastIndexOf('/') 29 | if (cut != -1) { 30 | result = result!!.substring(cut + 1) 31 | } 32 | } 33 | return result ?: "unknown" 34 | } 35 | 36 | internal fun Context.getIp(): String { 37 | val wifiManager = 38 | applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager? 39 | if (wifiManager == null) { 40 | MyLog.e("Failed to get phone IP address - WifiManager null") 41 | return "0.0.0.0" 42 | } 43 | val ipAddress = wifiManager.connectionInfo.ipAddress 44 | val ipAddressFormat = String.format( 45 | "%d.%d.%d.%d", 46 | ipAddress and 0xff, 47 | ipAddress shr 8 and 0xff, 48 | ipAddress shr 16 and 0xff, 49 | ipAddress shr 24 and 0xff 50 | ) 51 | MyLog.i("IP address: $ipAddressFormat") 52 | return ipAddressFormat 53 | } 54 | 55 | internal fun Context.getAppVersionName(): String { 56 | var v = "" 57 | try { 58 | v = packageManager.getPackageInfo(packageName, 0).versionName 59 | } catch (e: PackageManager.NameNotFoundException) { 60 | } 61 | return v 62 | } 63 | 64 | internal fun Context.getAppVersionCode(): Long { 65 | var v = 0L 66 | try { 67 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 68 | v = packageManager.getPackageInfo(packageName, 0).longVersionCode 69 | } else { 70 | @Suppress("DEPRECATION") 71 | v = packageManager.getPackageInfo(packageName, 0).versionCode.toLong() 72 | } 73 | } catch (e: PackageManager.NameNotFoundException) { 74 | } 75 | return v 76 | } 77 | 78 | internal fun Context.isOnWifi(): Boolean { 79 | val connectivityManager = 80 | getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? ?: return false 81 | connectivityManager.allNetworks.forEach { 82 | if (connectivityManager.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) return true 83 | } 84 | return false 85 | } 86 | 87 | internal fun Context.convertDpToPx(dp: Float): Float { 88 | return dp * this.resources.displayMetrics.density 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/gateway/ActivityHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.gateway 20 | 21 | import android.app.Instrumentation 22 | import android.content.Intent 23 | import android.util.SparseArray 24 | import androidx.fragment.app.Fragment 25 | import androidx.fragment.app.FragmentActivity 26 | import com.google.zxing.integration.android.IntentIntegrator 27 | import com.jim.sharetocomputer.AllOpen 28 | import com.jim.sharetocomputer.coroutines.TestableDispatchers 29 | import com.jim.sharetocomputer.logging.MyLog 30 | import kotlinx.coroutines.CompletableDeferred 31 | import kotlinx.coroutines.GlobalScope 32 | import kotlinx.coroutines.launch 33 | import kotlinx.coroutines.runBlocking 34 | 35 | @AllOpen 36 | class ActivityHelper { 37 | 38 | fun startActivityForResult( 39 | activity: FragmentActivity, 40 | intent: Intent 41 | ): Instrumentation.ActivityResult? { 42 | if (activity.isFinishing) { 43 | return null 44 | } 45 | val result = CompletableDeferred() 46 | val requestCode = (Math.random() * 1000).toInt() 47 | 48 | var fragment = activity.supportFragmentManager.findFragmentByTag(TAG) as FragmentHelper? 49 | if (fragment == null) { 50 | fragment = FragmentHelper() 51 | GlobalScope.launch(TestableDispatchers.Main) { 52 | MyLog.d("Add headless fragment") 53 | activity.supportFragmentManager 54 | .beginTransaction() 55 | .add(fragment, TAG) 56 | .commitNowAllowingStateLoss() 57 | } 58 | } 59 | fragment.addMapping(requestCode, result) 60 | 61 | GlobalScope.launch(TestableDispatchers.Main) { 62 | fragment.startActivityForResult(intent, requestCode) 63 | } 64 | return runBlocking { 65 | return@runBlocking result.await() 66 | } 67 | } 68 | 69 | 70 | fun startQrCodeScan(activity: FragmentActivity): Instrumentation.ActivityResult? { 71 | if (activity.isFinishing) { 72 | return null 73 | } 74 | val result = CompletableDeferred() 75 | val requestCode = IntentIntegrator.REQUEST_CODE 76 | 77 | var fragment = activity.supportFragmentManager.findFragmentByTag(TAG) as FragmentHelper? 78 | if (fragment==null) { 79 | fragment = FragmentHelper() 80 | GlobalScope.launch(TestableDispatchers.Main) { 81 | MyLog.d("Add headless fragment") 82 | activity.supportFragmentManager 83 | .beginTransaction() 84 | .add(fragment, TAG) 85 | .commitNowAllowingStateLoss() 86 | } 87 | } 88 | fragment.addMapping(requestCode, result) 89 | 90 | GlobalScope.launch(TestableDispatchers.Main) { 91 | IntentIntegrator.forSupportFragment(fragment).initiateScan() 92 | } 93 | return runBlocking { 94 | return@runBlocking result.await() 95 | } 96 | } 97 | 98 | } 99 | 100 | class FragmentHelper : Fragment() { 101 | 102 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 103 | super.onActivityResult(requestCode, resultCode, data) 104 | MyLog.i("onActivityResult $requestCode|$resultCode|${data?.extras?.keySet()}") 105 | map[requestCode].complete(Instrumentation.ActivityResult(resultCode, data)) 106 | map.remove(requestCode) 107 | } 108 | 109 | fun addMapping(requestCode: Int, result: CompletableDeferred) { 110 | MyLog.d("Add result code mapping $requestCode|") 111 | map.put(requestCode, result) 112 | } 113 | 114 | companion object { 115 | private val map = SparseArray>() 116 | } 117 | 118 | } 119 | 120 | private const val TAG = "startActivityForResult" 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/gateway/WifiApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.gateway 20 | 21 | import android.content.Context 22 | import android.net.wifi.WifiManager 23 | import com.jim.sharetocomputer.AllOpen 24 | import com.jim.sharetocomputer.logging.MyLog 25 | 26 | @AllOpen 27 | class WifiApi(val context: Context) { 28 | 29 | fun getIp(): String { 30 | val wifiManager = 31 | context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager? 32 | if (wifiManager == null) { 33 | MyLog.e("Failed to get phone IP address - WifiManager null") 34 | return "0.0.0.0" 35 | } 36 | val ipAddress = wifiManager.connectionInfo.ipAddress 37 | val ipAddressFormat = String.format( 38 | "%d.%d.%d.%d", 39 | ipAddress and 0xff, 40 | ipAddress shr 8 and 0xff, 41 | ipAddress shr 16 and 0xff, 42 | ipAddress shr 24 and 0xff 43 | ) 44 | MyLog.i("IP address: $ipAddressFormat") 45 | return ipAddressFormat 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/logging/KoinLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer.logging 18 | 19 | import org.koin.core.logger.Level 20 | import org.koin.core.logger.Logger 21 | import org.koin.core.logger.MESSAGE 22 | 23 | class KoinLogger : Logger() { 24 | 25 | override fun log(level: Level, msg: MESSAGE) { 26 | when (level) { 27 | Level.DEBUG -> MyLog.d(msg) 28 | Level.INFO -> MyLog.i(msg) 29 | Level.ERROR -> MyLog.e(msg) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/logging/MyLog.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer.logging 2 | 3 | import android.content.Context 4 | import com.elvishew.xlog.LogConfiguration 5 | import com.elvishew.xlog.LogLevel 6 | import com.elvishew.xlog.XLog 7 | import com.elvishew.xlog.flattener.ClassicFlattener 8 | import com.elvishew.xlog.printer.AndroidPrinter 9 | import com.elvishew.xlog.printer.file.FilePrinter 10 | import com.elvishew.xlog.printer.file.naming.ChangelessFileNameGenerator 11 | import com.jim.sharetocomputer.BuildConfig 12 | 13 | object MyLog { 14 | 15 | const val LOG_FILE = "mylog.log" 16 | private lateinit var logFolder: String 17 | 18 | fun d(msg: String) { 19 | XLog.tag(tag()).d(msg) 20 | } 21 | 22 | fun i(msg: String) { 23 | XLog.tag(tag()).i(msg) 24 | } 25 | 26 | fun w(msg: String, t: Throwable? = null) { 27 | if (t != null) { 28 | XLog.tag(tag()).w(msg, t) 29 | } else { 30 | XLog.tag(tag()).w(msg) 31 | } 32 | } 33 | 34 | fun e(msg: String, t: Throwable? = null) { 35 | if (t != null) { 36 | XLog.tag(tag()).e(msg, t) 37 | } else { 38 | XLog.tag(tag()).e(msg) 39 | } 40 | } 41 | 42 | fun logFilePath() = "$logFolder/$LOG_FILE" 43 | 44 | fun setupLogging(context: Context) { 45 | val config = LogConfiguration.Builder() 46 | .logLevel( 47 | if (BuildConfig.DEBUG) 48 | LogLevel.ALL 49 | else 50 | LogLevel.INFO 51 | ) 52 | .tag("[S2C]") 53 | .build() 54 | 55 | val androidPrinter = AndroidPrinter() 56 | logFolder = context.filesDir.absolutePath + "/log" 57 | val filePrinter = FilePrinter.Builder(logFolder) 58 | .fileNameGenerator(ChangelessFileNameGenerator(LOG_FILE)) // Default: ChangelessFileNameGenerator("log") 59 | .flattener(ClassicFlattener()) 60 | .build() 61 | 62 | XLog.init( 63 | config, 64 | androidPrinter, 65 | filePrinter 66 | ) 67 | 68 | } 69 | 70 | private fun tag(): String { 71 | val st = Throwable().stackTrace.first { 72 | it.className != MyLog.javaClass.name 73 | } 74 | return "${st.className}[${st.lineNumber}]" 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/logging/MyUncaughtExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer.logging 2 | 3 | class MyUncaughtExceptionHandler(private val handler: Thread.UncaughtExceptionHandler) : 4 | Thread.UncaughtExceptionHandler { 5 | override fun uncaughtException(t: Thread, e: Throwable) { 6 | MyLog.e("uncaughtException", e) 7 | handler.uncaughtException(t, e) 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/DummyActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jim.sharetocomputer.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | 5 | class DummyActivity : AppCompatActivity() 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/FragmentNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui 20 | 21 | import androidx.fragment.app.Fragment 22 | import androidx.navigation.fragment.findNavController 23 | import com.jim.sharetocomputer.ui.main.MainFragmentDirections 24 | 25 | class FragmentNavigation(val fragment: Fragment) { 26 | 27 | fun openAboutScreen() { 28 | fragment.findNavController() 29 | .navigate(MainFragmentDirections.actionFragmentMainToFragmentAbout()) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | package com.jim.sharetocomputer.ui.main 19 | 20 | import android.Manifest 21 | import android.content.Intent 22 | import android.content.pm.PackageManager 23 | import android.net.Uri 24 | import android.os.Bundle 25 | import androidx.appcompat.app.AppCompatActivity 26 | import androidx.core.app.ActivityCompat 27 | import androidx.databinding.DataBindingUtil 28 | import androidx.navigation.NavController 29 | import androidx.navigation.Navigation 30 | import com.jim.sharetocomputer.R 31 | import com.jim.sharetocomputer.ShareRequest 32 | import com.jim.sharetocomputer.databinding.ActivityMainBinding 33 | import com.jim.sharetocomputer.logging.MyLog 34 | 35 | class MainActivity : AppCompatActivity() { 36 | 37 | private lateinit var navController: NavController 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | MyLog.i("onCreate") 42 | DataBindingUtil.setContentView( 43 | this, 44 | R.layout.activity_main 45 | ) 46 | 47 | val request: ShareRequest? = intent.generateShareRequest() 48 | val bundle = if (request != null) { 49 | MainFragment.createBundle(request) 50 | } else { 51 | Bundle() 52 | } 53 | navController = Navigation.findNavController(this, R.id.main_nav_fragment) 54 | navController.setGraph(R.navigation.nav_main, bundle) 55 | 56 | if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED 57 | || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED 58 | ) { 59 | ActivityCompat.requestPermissions( 60 | this, 61 | arrayOf( 62 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 63 | Manifest.permission.READ_EXTERNAL_STORAGE 64 | ), 65 | 1 66 | ) 67 | } 68 | } 69 | 70 | override fun onDestroy() { 71 | MyLog.i("onDestroy") 72 | super.onDestroy() 73 | } 74 | 75 | private fun Intent.generateShareRequest(): ShareRequest? { 76 | if (action == Intent.ACTION_SEND && type?.startsWith("text") == true) { 77 | return ShareRequest.ShareRequestText(getStringExtra(Intent.EXTRA_TEXT) ?: "") 78 | } else if (action == Intent.ACTION_SEND) { 79 | getParcelableExtra(Intent.EXTRA_STREAM)?.let { uri -> 80 | return ShareRequest.ShareRequestSingleFile(uri) 81 | } 82 | } else if (action == Intent.ACTION_SEND_MULTIPLE) { 83 | getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { uris -> 84 | return ShareRequest.ShareRequestMultipleFile(uris) 85 | } 86 | } else { 87 | MyLog.w("Unknown action: $action|$type") 88 | } 89 | return null 90 | } 91 | 92 | override fun onSupportNavigateUp(): Boolean { 93 | return navController.navigateUp() || super.onSupportNavigateUp() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/main/MainFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.main 20 | 21 | import android.os.Bundle 22 | import android.view.LayoutInflater 23 | import android.view.View 24 | import android.view.ViewGroup 25 | import androidx.fragment.app.Fragment 26 | import com.jim.sharetocomputer.ShareRequest 27 | import com.jim.sharetocomputer.databinding.FragmentMainBinding 28 | import com.jim.sharetocomputer.logging.MyLog 29 | import org.koin.android.viewmodel.ext.android.viewModel 30 | 31 | class MainFragment : Fragment() { 32 | 33 | private val mainViewModel: MainViewModel by viewModel() 34 | 35 | override fun onCreateView( 36 | inflater: LayoutInflater, 37 | container: ViewGroup?, 38 | savedInstanceState: Bundle? 39 | ): View? { 40 | MyLog.i("onCreateView") 41 | val binding = FragmentMainBinding.inflate(inflater, container, false) 42 | 43 | val sectionsPagerAdapter = 44 | SectionsPagerAdapter(activity!!, childFragmentManager) 45 | binding.viewPager.adapter = sectionsPagerAdapter 46 | binding.tabs.setupWithViewPager(binding.viewPager) 47 | 48 | (arguments?.get(ARGS_REQUEST) as ShareRequest?)?.let { 49 | mainViewModel.setRequest(it) 50 | } 51 | 52 | return binding.root 53 | } 54 | 55 | override fun onDestroyView() { 56 | MyLog.i("onDestroyView") 57 | super.onDestroyView() 58 | } 59 | 60 | companion object { 61 | private const val ARGS_REQUEST = "request" 62 | 63 | fun createBundle(request: ShareRequest): Bundle { 64 | return Bundle().apply { 65 | putParcelable(ARGS_REQUEST, request) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.main 20 | 21 | import android.content.Context 22 | import android.os.Build 23 | import android.widget.Toast 24 | import androidx.annotation.StringRes 25 | import androidx.lifecycle.ViewModel 26 | import com.jim.sharetocomputer.AllOpen 27 | import com.jim.sharetocomputer.R 28 | import com.jim.sharetocomputer.ShareRequest 29 | import com.jim.sharetocomputer.WebServerService 30 | import com.jim.sharetocomputer.coroutines.TestableDispatchers 31 | import com.jim.sharetocomputer.ext.isOnWifi 32 | import com.jim.sharetocomputer.logging.MyLog 33 | import kotlinx.coroutines.GlobalScope 34 | import kotlinx.coroutines.launch 35 | 36 | @AllOpen 37 | class MainViewModel(val context: Context) : ViewModel() { 38 | 39 | fun setRequest(request: ShareRequest?) { 40 | if (request != null) { 41 | MyLog.i("request found $request") 42 | if (!checkWifi()) return 43 | startWebService(request) 44 | } else { 45 | MyLog.i("no request") 46 | } 47 | } 48 | 49 | protected fun startWebService(request: ShareRequest) { 50 | val intent = WebServerService.createIntent(context, request) 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 52 | MyLog.i("Starting web service foreground") 53 | context.startForegroundService(intent) 54 | } else { 55 | MyLog.i("Starting web service") 56 | context.startService(intent) 57 | } 58 | } 59 | 60 | protected fun checkWifi(): Boolean { 61 | if (context.isOnWifi()) return true 62 | MyLog.i("No Wi-Fi network detected") 63 | showToast(R.string.error_wifi_required) 64 | return false 65 | } 66 | 67 | protected fun showToast(@StringRes id: Int) { 68 | GlobalScope.launch(TestableDispatchers.Main) { 69 | Toast.makeText(context, id, Toast.LENGTH_LONG).show() 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/main/SectionsPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.main 20 | 21 | import android.content.Context 22 | import androidx.fragment.app.Fragment 23 | import androidx.fragment.app.FragmentManager 24 | import androidx.fragment.app.FragmentPagerAdapter 25 | import com.jim.sharetocomputer.R 26 | import com.jim.sharetocomputer.ui.receive.ReceiveFragment 27 | import com.jim.sharetocomputer.ui.send.SendFragment 28 | import com.jim.sharetocomputer.ui.setting.SettingFragment 29 | 30 | class SectionsPagerAdapter(private val context: Context, fm: FragmentManager) : 31 | FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 32 | 33 | override fun getItem(position: Int): Fragment { 34 | return when (position) { 35 | 0 -> SendFragment.newInstance() 36 | 1 -> ReceiveFragment.newInstance() 37 | else -> SettingFragment.newInstance() 38 | } 39 | } 40 | 41 | override fun getPageTitle(position: Int): CharSequence? { 42 | return context.resources.getString(TAB_TITLES[position]) 43 | } 44 | 45 | override fun getCount(): Int { 46 | return 3 47 | } 48 | 49 | companion object { 50 | 51 | private val TAB_TITLES = arrayOf( 52 | R.string.tab_title_send, 53 | R.string.tab_title_receive, 54 | R.string.tab_title_setting 55 | ) 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/receive/ReceiveFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.receive 20 | 21 | import android.os.Bundle 22 | import android.view.LayoutInflater 23 | import android.view.View 24 | import android.view.ViewGroup 25 | import androidx.fragment.app.Fragment 26 | import com.jim.sharetocomputer.databinding.FragmentReceiveBinding 27 | import com.jim.sharetocomputer.logging.MyLog 28 | import org.koin.android.viewmodel.ext.android.viewModel 29 | import org.koin.core.parameter.parametersOf 30 | 31 | class ReceiveFragment : Fragment() { 32 | 33 | private val viewModel: ReceiveViewModel by viewModel(parameters = { parametersOf(this) }) 34 | 35 | override fun onCreateView( 36 | inflater: LayoutInflater, 37 | container: ViewGroup?, 38 | savedInstanceState: Bundle? 39 | ): View? { 40 | MyLog.i("onCreate") 41 | val binding = FragmentReceiveBinding.inflate(inflater, container, false) 42 | binding.lifecycleOwner = this 43 | binding.viewModel = viewModel 44 | return binding.root 45 | } 46 | 47 | override fun onDestroyView() { 48 | MyLog.i("onDestroy") 49 | super.onDestroyView() 50 | } 51 | 52 | companion object { 53 | fun newInstance() = ReceiveFragment() 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/receive/ReceiveNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.receive 20 | 21 | import android.content.Intent 22 | import android.os.Build 23 | import android.widget.Toast 24 | import androidx.fragment.app.Fragment 25 | import com.google.gson.Gson 26 | import com.google.gson.JsonSyntaxException 27 | import com.google.zxing.integration.android.IntentIntegrator 28 | import com.google.zxing.integration.android.IntentResult 29 | import com.jim.sharetocomputer.AllOpen 30 | import com.jim.sharetocomputer.QrCodeInfo 31 | import com.jim.sharetocomputer.R 32 | import com.jim.sharetocomputer.WebUploadService 33 | import com.jim.sharetocomputer.coroutines.TestableDispatchers 34 | import com.jim.sharetocomputer.gateway.ActivityHelper 35 | import com.jim.sharetocomputer.logging.MyLog 36 | import kotlinx.coroutines.GlobalScope 37 | import kotlinx.coroutines.launch 38 | import org.koin.core.KoinComponent 39 | import kotlin.coroutines.resume 40 | import kotlin.coroutines.suspendCoroutine 41 | 42 | @AllOpen 43 | class ReceiveNavigation(val fragment: Fragment, val activityHelper: ActivityHelper): KoinComponent { 44 | 45 | suspend fun openScanQrCode() = suspendCoroutine { cont -> 46 | activityHelper.startQrCodeScan(fragment.activity!!)?.let { result -> 47 | val resultQrCode: IntentResult? = IntentIntegrator.parseActivityResult( 48 | IntentIntegrator.REQUEST_CODE, 49 | result.resultCode, 50 | result.resultData 51 | ) 52 | try { 53 | cont.resume(Gson().fromJson(resultQrCode!!.contents, QrCodeInfo::class.java)) 54 | return@suspendCoroutine 55 | } catch (e: JsonSyntaxException) { 56 | MyLog.w("Error on parsing QR Code result", e) 57 | GlobalScope.launch(TestableDispatchers.Main) { 58 | Toast.makeText( 59 | fragment.activity!!, 60 | R.string.warning_unknown_qrcode, 61 | Toast.LENGTH_LONG 62 | ).show() 63 | } 64 | } 65 | } 66 | cont.resume(null) 67 | } 68 | 69 | fun startWebUploadService() { 70 | fragment.context?.run { 71 | val intent = Intent(this, WebUploadService::class.java) 72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 73 | this.startForegroundService(intent) 74 | } else { 75 | this.startService(intent) 76 | } 77 | } 78 | } 79 | 80 | fun stopWebUploadService() { 81 | fragment.context?.run { 82 | stopService( 83 | Intent(this, WebUploadService::class.java) 84 | ) 85 | } 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/receive/ReceiveViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.receive 20 | 21 | import android.content.Context 22 | import android.widget.Toast 23 | import androidx.annotation.StringRes 24 | import androidx.core.content.ContextCompat 25 | import androidx.lifecycle.MediatorLiveData 26 | import androidx.lifecycle.MutableLiveData 27 | import androidx.lifecycle.ViewModel 28 | import com.jim.sharetocomputer.* 29 | import com.jim.sharetocomputer.coroutines.TestableDispatchers 30 | import com.jim.sharetocomputer.gateway.WifiApi 31 | import com.jim.sharetocomputer.logging.MyLog 32 | import kotlinx.coroutines.GlobalScope 33 | import kotlinx.coroutines.launch 34 | 35 | @AllOpen 36 | class ReceiveViewModel( 37 | val context: Context, 38 | val wifiApi: WifiApi, 39 | val navigation: ReceiveNavigation 40 | ) : ViewModel() { 41 | 42 | private val isSharing = MutableLiveData().apply { value = false } 43 | private val deviceIp = MutableLiveData().apply { value = "unknown" } 44 | private val isAbleToReceiveData = MediatorLiveData().apply { 45 | addSource(WebServerService.isRunning) { 46 | this.value = !it 47 | } 48 | } 49 | private val devicePort = WebUploadService.port 50 | 51 | fun scanQrCode() { 52 | MyLog.i("Select QrCode") 53 | GlobalScope.launch(TestableDispatchers.Default) { 54 | navigation.openScanQrCode()?.let { qrCodeInfo -> 55 | MyLog.i("Start download service to download from: $qrCodeInfo") 56 | ContextCompat.startForegroundService( 57 | context, DownloadService.createIntent(context, qrCodeInfo.url) 58 | ) 59 | if (qrCodeInfo.version > Application.QR_CODE_VERSION) { 60 | showToast(R.string.warning_newer_qrcode) 61 | } else { 62 | showToast(R.string.info_download_start) 63 | } 64 | 65 | } 66 | } 67 | } 68 | 69 | fun receiveFromComputer() { 70 | MyLog.i("Select start web") 71 | navigation.startWebUploadService() 72 | deviceIp.value = wifiApi.getIp() 73 | isSharing.value = true 74 | } 75 | 76 | fun stopWeb() { 77 | navigation.stopWebUploadService() 78 | isSharing.value = false 79 | } 80 | 81 | private fun showToast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = 82 | GlobalScope.launch(TestableDispatchers.Main) { 83 | Toast.makeText(context, id, duration).show() 84 | } 85 | 86 | 87 | fun isAbleToReceive() = isAbleToReceiveData 88 | 89 | fun isSharing() = isSharing 90 | fun deviceIp() = deviceIp 91 | fun devicePort() = devicePort 92 | 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/send/SendFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | package com.jim.sharetocomputer.ui.send 19 | 20 | import android.os.Bundle 21 | import android.view.LayoutInflater 22 | import android.view.View 23 | import android.view.ViewGroup 24 | import androidx.fragment.app.Fragment 25 | import com.jim.sharetocomputer.ShareRequest 26 | import com.jim.sharetocomputer.databinding.FragmentSendBinding 27 | import com.jim.sharetocomputer.logging.MyLog 28 | import org.koin.android.viewmodel.ext.android.viewModel 29 | 30 | 31 | class SendFragment : Fragment() { 32 | 33 | private val sendViewModel: SendViewModel by viewModel() 34 | 35 | override fun onCreateView( 36 | inflater: LayoutInflater, 37 | container: ViewGroup?, 38 | savedInstanceState: Bundle? 39 | ): View? { 40 | MyLog.i("onCreate") 41 | sendViewModel.activity = activity!! 42 | val binding = FragmentSendBinding.inflate(inflater, container, false) 43 | binding.lifecycleOwner = this 44 | binding.viewModel = sendViewModel 45 | return binding.root 46 | } 47 | 48 | override fun onDestroyView() { 49 | MyLog.i("onDestroy") 50 | super.onDestroyView() 51 | } 52 | 53 | companion object { 54 | private const val ARGS_REQUEST = "request" 55 | 56 | fun createBundle(request: ShareRequest): Bundle { 57 | return Bundle().apply { 58 | putParcelable(ARGS_REQUEST, request) 59 | } 60 | } 61 | 62 | fun newInstance() = SendFragment() 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/send/SendViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.send 20 | 21 | import android.app.Instrumentation 22 | import android.content.Context 23 | import android.content.Intent 24 | import android.graphics.Bitmap 25 | import android.graphics.drawable.BitmapDrawable 26 | import android.graphics.drawable.Drawable 27 | import android.net.Uri 28 | import android.provider.MediaStore 29 | import androidx.appcompat.app.AppCompatActivity 30 | import androidx.fragment.app.FragmentActivity 31 | import androidx.lifecycle.LiveData 32 | import androidx.lifecycle.MediatorLiveData 33 | import androidx.lifecycle.MutableLiveData 34 | import com.google.gson.Gson 35 | import com.google.zxing.BarcodeFormat 36 | import com.jim.sharetocomputer.* 37 | import com.jim.sharetocomputer.coroutines.TestableDispatchers 38 | import com.jim.sharetocomputer.ext.convertDpToPx 39 | import com.jim.sharetocomputer.gateway.ActivityHelper 40 | import com.jim.sharetocomputer.gateway.WifiApi 41 | import com.jim.sharetocomputer.logging.MyLog 42 | import com.jim.sharetocomputer.ui.main.MainViewModel 43 | import com.journeyapps.barcodescanner.BarcodeEncoder 44 | import kotlinx.coroutines.GlobalScope 45 | import kotlinx.coroutines.launch 46 | 47 | @AllOpen 48 | class SendViewModel(context: Context, val wifiApi: WifiApi, val activityHelper: ActivityHelper) : 49 | MainViewModel(context) { 50 | 51 | private val deviceIp = MutableLiveData().apply { value = "unknown" } 52 | private val isAbleToShareData = MediatorLiveData().apply { 53 | addSource(WebUploadService.isRunning) { 54 | this.value = !it 55 | } 56 | } 57 | private val devicePort = WebServerService.port 58 | private var qrCode = MutableLiveData() 59 | private var qrCodeBitmap: Bitmap? = null 60 | lateinit var activity: FragmentActivity 61 | 62 | init { 63 | updateWebServerUi() 64 | } 65 | 66 | fun selectFile() { 67 | MyLog.i("Select File") 68 | if (!checkWifi()) return 69 | GlobalScope.launch(TestableDispatchers.Default) { 70 | val intent = 71 | Intent(Intent.ACTION_OPEN_DOCUMENT).apply { 72 | addCategory(Intent.CATEGORY_OPENABLE) 73 | type = "*/*" 74 | putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) 75 | } 76 | activityHelper.startActivityForResult(activity, intent)?.let { result -> 77 | handleSelectFileResult(result) 78 | } 79 | } 80 | } 81 | 82 | fun selectMedia() { 83 | MyLog.i("Select Media") 84 | if (!checkWifi()) return 85 | GlobalScope.launch(TestableDispatchers.Default) { 86 | val intent = 87 | Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply { 88 | type = "*/*" 89 | putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) 90 | } 91 | activityHelper.startActivityForResult(activity, intent)?.let { result -> 92 | handleSelectFileResult(result) 93 | } 94 | } 95 | } 96 | 97 | fun isAbleToShare(): LiveData = isAbleToShareData 98 | 99 | private fun handleSelectFileResult(result: Instrumentation.ActivityResult) { 100 | MyLog.i("*Result: ${result.resultCode}|${result.resultData?.extras?.keySet()}") 101 | if (result.resultCode == AppCompatActivity.RESULT_OK) { 102 | updateWebServerUi() 103 | result.resultData.data?.run { 104 | startWebService(ShareRequest.ShareRequestSingleFile(this)) 105 | } 106 | result.resultData.clipData?.run { 107 | if (this.itemCount == 1) { 108 | startWebService(ShareRequest.ShareRequestSingleFile(this.getItemAt(0).uri)) 109 | } else { 110 | val uris = mutableListOf() 111 | for (i in 0 until this.itemCount) { 112 | uris.add(this.getItemAt(i).uri) 113 | } 114 | startWebService(ShareRequest.ShareRequestMultipleFile(uris)) 115 | } 116 | } 117 | } 118 | } 119 | 120 | private fun updateWebServerUi() { 121 | GlobalScope.launch(TestableDispatchers.Main) { 122 | deviceIp.value = wifiApi.getIp() 123 | qrCodeBitmap?.recycle() 124 | qrCodeBitmap = generateQrCode() 125 | qrCode.value = BitmapDrawable(context.resources, qrCodeBitmap) 126 | } 127 | } 128 | 129 | private fun generateQrCode(): Bitmap { 130 | val barcodeEncoder = BarcodeEncoder() 131 | val barcodeContent = Gson().toJson( 132 | QrCodeInfo( 133 | Application.QR_CODE_VERSION, 134 | context.getString( 135 | R.string.qrcode_url, 136 | wifiApi.getIp(), 137 | devicePort.value.toString() 138 | ) 139 | ) 140 | ) 141 | return barcodeEncoder.encodeBitmap( 142 | barcodeContent, 143 | BarcodeFormat.QR_CODE, 144 | context.convertDpToPx(200F).toInt(), context.convertDpToPx(200F).toInt() 145 | ) 146 | } 147 | 148 | fun stopShare() { 149 | stopWebService() 150 | } 151 | 152 | 153 | private fun stopWebService() { 154 | val intent = WebServerService.createIntent(context, null) 155 | context.stopService(intent) 156 | } 157 | 158 | fun isSharing(): MutableLiveData { 159 | return WebServerService.isRunning 160 | } 161 | 162 | fun deviceIp() = deviceIp 163 | fun devicePort() = devicePort 164 | fun qrCode() = qrCode 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/setting/AboutFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer.ui.setting 18 | 19 | import android.os.Bundle 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import androidx.fragment.app.Fragment 24 | import com.jim.sharetocomputer.databinding.FragmentAboutBinding 25 | 26 | class AboutFragment : Fragment() { 27 | 28 | override fun onCreateView( 29 | inflater: LayoutInflater, 30 | container: ViewGroup?, 31 | savedInstanceState: Bundle? 32 | ): View? { 33 | val binding = FragmentAboutBinding.inflate(inflater, container, false) 34 | return binding.root 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/setting/SettingFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.setting 20 | 21 | import android.content.Context 22 | import android.content.Intent 23 | import android.os.Build 24 | import android.os.Bundle 25 | import android.os.Environment 26 | import android.view.LayoutInflater 27 | import android.view.View 28 | import android.view.ViewGroup 29 | import android.widget.Toast 30 | import androidx.core.content.FileProvider 31 | import androidx.preference.Preference 32 | import androidx.preference.PreferenceFragmentCompat 33 | import com.jim.sharetocomputer.Application 34 | import com.jim.sharetocomputer.R 35 | import com.jim.sharetocomputer.ext.getAppVersionCode 36 | import com.jim.sharetocomputer.ext.getAppVersionName 37 | import com.jim.sharetocomputer.ext.getIp 38 | import com.jim.sharetocomputer.ext.isOnWifi 39 | import com.jim.sharetocomputer.logging.MyLog 40 | import org.koin.android.ext.android.inject 41 | import org.koin.core.parameter.parametersOf 42 | import java.io.* 43 | 44 | 45 | class SettingFragment : PreferenceFragmentCompat() { 46 | 47 | private val navigation by inject(parameters = { parametersOf(this) }) 48 | 49 | override fun onCreateView( 50 | inflater: LayoutInflater, 51 | container: ViewGroup?, 52 | savedInstanceState: Bundle? 53 | ): View? { 54 | MyLog.i("onCreate") 55 | return super.onCreateView(inflater, container, savedInstanceState) 56 | } 57 | 58 | override fun onDestroyView() { 59 | MyLog.i("onDestroy") 60 | super.onDestroyView() 61 | } 62 | 63 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 64 | setPreferencesFromResource(R.xml.setting, rootKey) 65 | } 66 | 67 | override fun onPreferenceTreeClick(preference: Preference): Boolean { 68 | when (preference.title) { 69 | getString(R.string.title_download_log_preference) -> copyLogFile() 70 | getString(R.string.title_send_feedback_preference) -> sendFeedback() 71 | getString(R.string.title_about_preference) -> navigation.openAboutScreen() 72 | else -> return super.onPreferenceTreeClick(preference) 73 | } 74 | return true 75 | } 76 | 77 | private fun sendFeedback() { 78 | val logFileUri = FileProvider.getUriForFile( 79 | activity!!, "com.jim.sharetocomputer.provider", 80 | File(MyLog.logFilePath()) 81 | ) 82 | val intent = Intent(Intent.ACTION_SEND).apply { 83 | type = "message/rfc822" 84 | putExtra(Intent.EXTRA_EMAIL, arrayOf(Application.EMAIL_ADDRESS)) 85 | putExtra(Intent.EXTRA_TEXT, getDeviceInfo(activity!!)) 86 | putExtra(Intent.EXTRA_SUBJECT, activity!!.getString(R.string.feedback_mail_title)) 87 | putExtra(Intent.EXTRA_STREAM, logFileUri) 88 | } 89 | if (intent.resolveActivity(activity!!.packageManager) != null) { 90 | startActivity(intent) 91 | } else { 92 | MyLog.w("No mail application found") 93 | Toast.makeText(activity, R.string.error_fail_send_feedback, Toast.LENGTH_SHORT).show() 94 | } 95 | } 96 | 97 | private fun getDeviceInfo(context: Context): String? { 98 | val info = 99 | StringBuilder("${context.getString(R.string.feedback_email_body)}\n\n--------------------\n") 100 | info.append("Version: ${context.getAppVersionName()}(${context.getAppVersionCode()})\n") 101 | info.append("Phone: ${Build.BRAND}|${Build.MODEL}|${Build.BOARD}|${Build.DEVICE}\n") 102 | info.append("Wifi: ${context.isOnWifi()}|${context.getIp()}\n") 103 | return info.toString() 104 | } 105 | 106 | @Suppress("DEPRECATION") 107 | private fun copyLogFile() { 108 | val source = File(MyLog.logFilePath()) 109 | val target = 110 | File( 111 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), 112 | MyLog.LOG_FILE 113 | ) 114 | try { 115 | copyFile(source, target) 116 | Toast.makeText(activity, R.string.info_download_log_complete, Toast.LENGTH_SHORT).show() 117 | } catch (e: IOException) { 118 | MyLog.w("Fail copying log", e) 119 | } 120 | } 121 | 122 | private fun copyFile(source: File, target: File) { 123 | BufferedInputStream(FileInputStream(source)).use { input -> 124 | val data = input.readBytes() 125 | BufferedOutputStream(FileOutputStream(target)).use { output -> 126 | output.write(data) 127 | } 128 | } 129 | } 130 | 131 | companion object { 132 | 133 | fun newInstance() = SettingFragment() 134 | 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/ui/setting/SettingNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | * 4 | * Share To Computer is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Share To Computer is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Share To Computer. If not, see . 16 | * 17 | */ 18 | 19 | package com.jim.sharetocomputer.ui.setting 20 | 21 | import androidx.fragment.app.Fragment 22 | import androidx.navigation.fragment.findNavController 23 | import com.jim.sharetocomputer.AllOpen 24 | import com.jim.sharetocomputer.ui.main.MainFragmentDirections 25 | 26 | @AllOpen 27 | class SettingNavigation(val fragment: Fragment) { 28 | 29 | fun openAboutScreen() { 30 | fragment.findNavController() 31 | .navigate(MainFragmentDirections.actionFragmentMainToFragmentAbout()) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/webserver/InputStreamNotifyWebServer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer.webserver 18 | 19 | import java.io.InputStream 20 | 21 | class InputStreamNotifyWebServer( 22 | private val wrappedInputStream: InputStream, 23 | private val webServer: WebServer 24 | ) : 25 | InputStream() { 26 | 27 | override fun read(): Int { 28 | notifyWebServer() 29 | return wrappedInputStream.read() 30 | } 31 | 32 | override fun read(b: ByteArray): Int { 33 | notifyWebServer() 34 | return wrappedInputStream.read(b) 35 | } 36 | 37 | override fun read(b: ByteArray, off: Int, len: Int): Int { 38 | notifyWebServer() 39 | return wrappedInputStream.read(b, off, len) 40 | } 41 | 42 | 43 | private fun notifyWebServer() { 44 | webServer.notifyAccess() 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/webserver/WebServer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer.webserver 18 | 19 | import com.google.gson.Gson 20 | import com.jim.sharetocomputer.FileInfo 21 | import com.jim.sharetocomputer.ShareInfo 22 | import com.jim.sharetocomputer.logging.MyLog 23 | import fi.iki.elonen.NanoHTTPD 24 | import java.io.ByteArrayInputStream 25 | 26 | open class WebServer(port: Int) : NanoHTTPD(port) { 27 | 28 | var lastAccessTime: Long = System.currentTimeMillis() 29 | protected set 30 | 31 | protected fun infoResponse(total: Int, files: List): Response { 32 | val shareInfo = ShareInfo(total, files) 33 | val inputStream = ByteArrayInputStream(Gson().toJson(shareInfo).toByteArray()) 34 | return newFixedLengthResponse( 35 | Response.Status.OK, 36 | "application/json", 37 | InputStreamNotifyWebServer(inputStream, this), 38 | -1 39 | ) 40 | } 41 | 42 | open fun notifyAccess() { 43 | lastAccessTime = System.currentTimeMillis() 44 | } 45 | 46 | override fun start() { 47 | MyLog.i("Starting WebServer") 48 | super.start() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/webserver/WebServerSingleFile.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer.webserver 18 | 19 | import android.content.ClipDescription 20 | import android.content.Context 21 | import android.net.Uri 22 | import com.jim.sharetocomputer.FileInfo 23 | import com.jim.sharetocomputer.Message 24 | import com.jim.sharetocomputer.ext.getFileName 25 | import com.jim.sharetocomputer.logging.MyLog 26 | 27 | class WebServerSingleFile(private val context: Context, port: Int) : WebServer(port) { 28 | 29 | private var uri: Uri? = null 30 | 31 | fun setUri(value: Uri) { 32 | uri = value 33 | } 34 | 35 | override fun serve(session: IHTTPSession?): Response { 36 | MyLog.i("Incoming http request from ${session?.remoteIpAddress}(${session?.remoteHostName}) to ${session?.uri}") 37 | notifyAccess() 38 | if (uri == null || session == null) { 39 | MyLog.w("Empty uri($uri) or session($session)") 40 | return newFixedLengthResponse( 41 | Response.Status.NOT_FOUND, 42 | ClipDescription.MIMETYPE_TEXT_PLAIN, 43 | Message.ERROR_CONTENT_NOT_SET 44 | ) 45 | } else if (session.uri == "/info") { 46 | return infoResponse(1, listOf(FileInfo(context.getFileName(uri!!)))) 47 | } else { 48 | val fis = context.contentResolver.openInputStream(uri!!) 49 | MyLog.d("*Response:$uri") 50 | return newFixedLengthResponse( 51 | Response.Status.OK, 52 | null, 53 | InputStreamNotifyWebServer(fis!!, this), 54 | -1 55 | ).apply { 56 | addHeader("Content-Disposition", "filename=\"${context.getFileName(uri!!)}\"") 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jim/sharetocomputer/webserver/WebServerText.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Share To Computer Copyright (C) 2019 Jimmy . 3 | 4 | Share To Computer is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Share To Computer is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Share To Computer. If not, see . 16 | */ 17 | package com.jim.sharetocomputer.webserver 18 | 19 | import android.content.ClipDescription 20 | import com.jim.sharetocomputer.FileInfo 21 | import com.jim.sharetocomputer.Message 22 | import com.jim.sharetocomputer.logging.MyLog 23 | 24 | class WebServerText(port: Int) : WebServer(port) { 25 | 26 | private var text: String? = null 27 | private val filename = "${System.currentTimeMillis()}.txt" 28 | 29 | override fun serve(session: IHTTPSession?): Response { 30 | notifyAccess() 31 | MyLog.i("Incoming http request from ${session?.remoteIpAddress}(${session?.remoteHostName}) to ${session?.uri}") 32 | return if (text == null || session == null) { 33 | newFixedLengthResponse( 34 | Response.Status.NOT_FOUND, 35 | ClipDescription.MIMETYPE_TEXT_PLAIN, 36 | Message.ERROR_CONTENT_NOT_SET 37 | ) 38 | } else if (session.uri == "/info") { 39 | infoResponse(1, listOf(FileInfo(filename))) 40 | } else { 41 | newFixedLengthResponse( 42 | Response.Status.OK, 43 | ClipDescription.MIMETYPE_TEXT_PLAIN, 44 | text 45 | ).apply { 46 | addHeader("Content-Disposition", "filename=\"$filename\"") 47 | } 48 | } 49 | } 50 | 51 | fun setText(value: String) { 52 | text = value 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_round_corner_primary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 12 | 13 | 18 | 19 | 28 | 29 | 30 | 37 | 38 | 48 | 49 | 57 | 58 | 66 | 67 | 75 | 76 | 84 | 85 | 93 | 94 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | 23 | 24 | 25 | 30 | 31 | 36 | 37 | 47 | 48 | 55 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_receive.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 42 | 43 |