├── app ├── src │ ├── .gitignore │ ├── main │ │ ├── res │ │ │ ├── mipmap-anydpi │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_secure.png │ │ │ │ └── ic_insecure.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_secure.png │ │ │ │ └── ic_insecure.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_insecure.png │ │ │ │ └── ic_secure.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_secure.png │ │ │ │ └── ic_insecure.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_secure.png │ │ │ │ └── ic_insecure.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── themes.xml │ │ │ │ ├── colors.xml │ │ │ │ └── strings.xml │ │ │ ├── xml │ │ │ │ └── provider_paths.xml │ │ │ ├── drawable │ │ │ │ ├── open_icon_resized.xml │ │ │ │ ├── share_icon_resized.xml │ │ │ │ ├── history_icon_resized.xml │ │ │ │ ├── ic_upload_icon.xml │ │ │ │ ├── ic_download_icon.xml │ │ │ │ ├── history_icon.xml │ │ │ │ ├── open_icon.xml │ │ │ │ ├── share_icon.xml │ │ │ │ └── clip_share_icon_mono.xml │ │ │ ├── layout │ │ │ │ ├── activity_invisible.xml │ │ │ │ ├── tunnel_switch.xml │ │ │ │ ├── popup_elem.xml │ │ │ │ ├── popup_servers.xml │ │ │ │ ├── list_element.xml │ │ │ │ ├── popup_reset.xml │ │ │ │ └── popup_display.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── menu │ │ │ │ ├── settings_action_bar.xml │ │ │ │ └── action_bar.xml │ │ │ ├── values-night │ │ │ │ ├── themes.xml │ │ │ │ └── colors.xml │ │ │ └── layout-v26 │ │ │ │ ├── popup_elem.xml │ │ │ │ └── list_element.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── com │ │ │ │ └── tw │ │ │ │ └── clipshare │ │ │ │ ├── PendingTask.java │ │ │ │ ├── netConnection │ │ │ │ ├── TunnelConnection.java │ │ │ │ ├── PlainConnection.java │ │ │ │ ├── ServerConnection.java │ │ │ │ ├── SecureConnection.java │ │ │ │ └── TunnelManager.java │ │ │ │ ├── platformUtils │ │ │ │ ├── directoryTree │ │ │ │ │ ├── RegularFile.java │ │ │ │ │ ├── Directory.java │ │ │ │ │ └── DirectoryTreeNode.java │ │ │ │ ├── DataContainer.java │ │ │ │ ├── AndroidUtils.java │ │ │ │ └── StatusNotifier.java │ │ │ │ ├── InvisibleActivity.java │ │ │ │ ├── protocol │ │ │ │ ├── ProtoV1.java │ │ │ │ ├── ProtoV2.java │ │ │ │ ├── ProtoV3.java │ │ │ │ ├── Proto.java │ │ │ │ └── ProtocolSelector.java │ │ │ │ ├── CertUtils.java │ │ │ │ ├── Utils.java │ │ │ │ ├── TCPScanner.java │ │ │ │ ├── BackgroundService.java │ │ │ │ ├── ServerFinder.java │ │ │ │ └── FileService.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── com │ │ └── tw │ │ └── clipshare │ │ ├── UseAppContextTest.java │ │ ├── netConnection │ │ └── MockConnection.java │ │ ├── UtilsTest.java │ │ ├── proto │ │ ├── BAOStreamBuilder.java │ │ └── ProtocolSelectorTest.java │ │ └── platformUtils │ │ ├── AndroidUtilsTest.java │ │ ├── TimeContainerTest.java │ │ └── StatusNotifierTest.java ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── resources │ └── release_notes.md │ ├── build-test.yml │ ├── check-style.yml │ └── release.yml ├── fastlane └── metadata │ └── android │ └── en-US │ ├── title.txt │ ├── short_description.txt │ ├── images │ ├── icon.png │ ├── featureGraphic.png │ └── phoneScreenshots │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ └── 5.jpg │ └── full_description.txt ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .gitattributes ├── .yamllint ├── LICENSE ├── gradle.properties ├── gradlew.bat └── gradlew /app/src/.gitignore: -------------------------------------------------------------------------------- 1 | /test -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @thevindu-w 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | ClipShare -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | /latest 4 | /legacy 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "ClipShare" 2 | include ':app' 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Share clipboard between devices. Securely share files, text, and screenshots. -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-hdpi/ic_secure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-mdpi/ic_secure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_insecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-hdpi/ic_insecure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_insecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-mdpi/ic_insecure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_insecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-xhdpi/ic_insecure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-xhdpi/ic_secure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-xxhdpi/ic_secure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-xxxhdpi/ic_secure.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_insecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-xxhdpi/ic_insecure.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_insecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/drawable-xxxhdpi/ic_insecure.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #E8E8E8 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thevindu-w/clip_share_client/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.apk 3 | *.class 4 | *.jks 5 | .gradle 6 | local.properties 7 | .idea/ 8 | .DS_Store 9 | build/ 10 | captures/ 11 | .externalNativeBuild 12 | .cxx 13 | TODO 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.jar binary 4 | *.jpg binary 5 | *.png binary 6 | 7 | *.cmd text eol=crlf 8 | *.bat text eol=crlf 9 | 10 | *.sh text eol=lf 11 | /gradlew text eol=lf 12 | *.html text eol=lf 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/open_icon_resized.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/share_icon_resized.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/history_icon_resized.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_invisible.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | github-actions: 9 | patterns: 10 | - "*" 11 | 12 | - package-ecosystem: "gradle" 13 | directory: "app/" 14 | schedule: 15 | interval: "weekly" 16 | groups: 17 | gradle: 18 | patterns: 19 | - "*" 20 | -------------------------------------------------------------------------------- /app/src/main/res/menu/settings_action_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_upload_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFF 4 | #5000 5 | #FFFF 6 | #FDDD 7 | #FCCF 8 | #FDDE 9 | #F0C0 10 | #FF00 11 | #F444 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F000 4 | #5FFF 5 | #F000 6 | #F333 7 | #F115 8 | #F223 9 | #F0D0 10 | #FF11 11 | #FBBB 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/history_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | yaml-files: 2 | - '*.yaml' 3 | - '*.yml' 4 | - '.yamllint' 5 | 6 | rules: 7 | braces: enable 8 | brackets: enable 9 | colons: enable 10 | commas: enable 11 | comments: enable 12 | comments-indentation: enable 13 | document-start: false 14 | empty-lines: enable 15 | empty-values: enable 16 | hyphens: enable 17 | indentation: 18 | spaces: 2 19 | indent-sequences: false 20 | key-duplicates: enable 21 | line-length: 22 | max: 120 23 | level: warning 24 | new-line-at-end-of-file: enable 25 | new-lines: enable 26 | octal-values: enable 27 | trailing-spaces: enable 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tunnel_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tw/clipshare/UseAppContextTest.java: -------------------------------------------------------------------------------- 1 | package com.tw.clipshare; 2 | 3 | import android.content.Context; 4 | import androidx.test.ext.junit.runners.AndroidJUnit4; 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import junit.framework.TestCase; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | /** 11 | * Instrumented test, which will execute on an Android device. 12 | * 13 | * @see Testing documentation 14 | */ 15 | @RunWith(AndroidJUnit4.class) 16 | public class UseAppContextTest extends TestCase { 17 | @Test 18 | public void testUseAppContext() { 19 | // Context of the app under test. 20 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/open_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/resources/release_notes.md: -------------------------------------------------------------------------------- 1 | This is version of the Android client of ClipShare, which supports protocol versions 1, 2, and 3. 2 | You will need the ClipShare server running on a Windows, macOS, or Linux machine to connect. You can download the server at [github.com/thevindu-w/clip_share_server/releases](https://github.com/thevindu-w/clip_share_server/releases). 3 | 4 | There are 2 APK files included in the release assets for compatibility. 5 | - `clip_share_client.apk` - Recommended for Android 9 (API level 28) and above. 6 | - `clip_share_client-legacy.apk` - Supports Android 7 (API level 24) and above. 7 | 8 | Refer to the [README](https://github.com/thevindu-w/clip_share_client/#how-to-use) for usage information.
9 | **Changes:** 10 | - Add a new settings option to reset settings. 11 | - Minor bug fixes. 12 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/res/menu/action_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v26/popup_elem.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popup_elem.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/share_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popup_servers.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 24 | 25 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 \ 10 | --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ 11 | --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ 12 | --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ 13 | --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ 14 | --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ 15 | --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 16 | # When configured, Gradle will run in incubating parallel mode. 17 | # This option should only be used with decoupled projects. More details, visit 18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 19 | # org.gradle.parallel=true 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/PendingTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare; 26 | 27 | import com.tw.clipshare.platformUtils.AndroidUtils; 28 | import com.tw.clipshare.protocol.Proto; 29 | 30 | public record PendingTask(Proto proto, AndroidUtils utils, int task) { 31 | public static final int GET_FILES = 3; 32 | public static final int SEND_FILES = 4; 33 | } 34 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 |

Share the clipboard between your phone and desktop. Securely share text, files, and screenshots. 2 | ClipShare is a simple, lightweight, and cross-platform app for sharing copied text, files, and screenshots between an Android phone and a desktop.

3 |

4 | Features 5 |

15 | Configuration options (of the Android app) 16 | 25 |

26 |

This is the Android client of ClipShare. You need the server program running on your desktop to connect with it. The server is available for Windows, macOS, and Linux. You can find the server here on GitHub. 27 | A desktop client for ClipShare is also available on GitHub.

28 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v26/list_element.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 20 | 21 | 35 | 36 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_element.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 21 | 22 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/netConnection/TunnelConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2023 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.netConnection; 26 | 27 | import java.io.IOException; 28 | import java.net.Socket; 29 | import java.net.SocketException; 30 | 31 | /** 32 | * @noinspection unused 33 | */ 34 | public class TunnelConnection extends ServerConnection { 35 | public TunnelConnection(String address) throws IOException { 36 | super(); 37 | Socket tunnel = TunnelManager.getConnection(address); 38 | if (tunnel == null) { 39 | throw new SocketException("No tunnel available for " + address); 40 | } 41 | this.socket = tunnel; 42 | this.inStream = this.socket.getInputStream(); 43 | this.outStream = this.socket.getOutputStream(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/platformUtils/directoryTree/RegularFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.platformUtils.directoryTree; 26 | 27 | import android.net.Uri; 28 | 29 | public class RegularFile extends DirectoryTreeNode { 30 | 31 | public final Uri uri; 32 | public long size; 33 | 34 | public RegularFile(Uri uri, Directory parent) { 35 | super(null, parent); 36 | this.uri = uri; 37 | } 38 | 39 | public RegularFile(Uri uri) { 40 | this(uri, null); 41 | } 42 | 43 | @Override 44 | public int getLeafCount(boolean includeLeafDirs) { 45 | return 1; 46 | } 47 | 48 | @Override 49 | public long getFileSize() { 50 | return this.size; 51 | } 52 | 53 | @Override 54 | public Uri getUri() { 55 | return this.uri; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tw/clipshare/netConnection/MockConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.netConnection; 26 | 27 | import java.io.ByteArrayOutputStream; 28 | import java.io.InputStream; 29 | import java.io.OutputStream; 30 | 31 | public class MockConnection extends ServerConnection { 32 | public MockConnection(InputStream inputStream, OutputStream outputStream) { 33 | super(); 34 | this.inStream = inputStream; 35 | this.outStream = outputStream; 36 | } 37 | 38 | public MockConnection(InputStream inputStream) { 39 | this(inputStream, new ByteArrayOutputStream()); 40 | } 41 | 42 | public byte[] getOutputBytes() { 43 | ByteArrayOutputStream ostream = (ByteArrayOutputStream) this.outStream; 44 | return ostream.toByteArray(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/netConnection/PlainConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.netConnection; 26 | 27 | import java.io.IOException; 28 | import java.net.InetAddress; 29 | import java.net.InetSocketAddress; 30 | import java.net.Socket; 31 | 32 | public class PlainConnection extends ServerConnection { 33 | 34 | /** 35 | * Unencrypted TCP connection to the server. 36 | * 37 | * @param serverAddress address of the server 38 | * @param port port on which the server is listening 39 | * @throws IOException on socket connection error 40 | */ 41 | public PlainConnection(InetAddress serverAddress, int port) throws IOException { 42 | super(new Socket()); 43 | this.socket.connect(new InetSocketAddress(serverAddress, port), 500); 44 | this.inStream = this.socket.getInputStream(); 45 | this.outStream = this.socket.getOutputStream(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/platformUtils/directoryTree/Directory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.platformUtils.directoryTree; 26 | 27 | import android.net.Uri; 28 | import java.util.ArrayList; 29 | 30 | public class Directory extends DirectoryTreeNode { 31 | public final ArrayList children; 32 | 33 | public Directory(String name, int size, Directory parent) { 34 | super(name, parent); 35 | this.children = new ArrayList<>(size); 36 | } 37 | 38 | @Override 39 | public int getLeafCount(boolean includeLeafDirs) { 40 | int leaves = 0; 41 | for (DirectoryTreeNode child : children) { 42 | leaves += child.getLeafCount(includeLeafDirs); 43 | } 44 | if (leaves == 0 && includeLeafDirs) leaves = 1; 45 | return leaves; 46 | } 47 | 48 | @Override 49 | public long getFileSize() { 50 | return -1; 51 | } 52 | 53 | @Override 54 | public Uri getUri() { 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tw/clipshare/UtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare; 26 | 27 | import static org.junit.Assert.*; 28 | 29 | import androidx.test.ext.junit.runners.AndroidJUnit4; 30 | import org.junit.Test; 31 | import org.junit.runner.RunWith; 32 | 33 | @RunWith(AndroidJUnit4.class) 34 | public class UtilsTest { 35 | @Test 36 | public void isValidIPTest() { 37 | String[] validIPv4s = {"192.168.1.1", "0.0.0.0"}; 38 | for (String ip : validIPv4s) { 39 | assertTrue(Utils.isValidIP(ip)); 40 | } 41 | String[] validIPv6s = {"::", "fc00:abcd::123", "fe80::1", "::1"}; 42 | for (String ip : validIPv6s) { 43 | assertTrue(Utils.isValidIP(ip)); 44 | } 45 | String[] invalidIPv4s = {"192.168.1.1.1", "127.0.0.256"}; 46 | for (String ip : invalidIPv4s) { 47 | assertFalse(Utils.isValidIP(ip)); 48 | } 49 | String[] invalidIPv6s = {":::", "fc00", "fe80:", ":1", "fe80:abcg::1", " fe80::1"}; 50 | for (String ip : invalidIPv6s) { 51 | assertFalse(Utils.isValidIP(ip)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/InvisibleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare; 26 | 27 | import android.app.Activity; 28 | import android.os.Bundle; 29 | import android.view.WindowManager; 30 | import com.tw.clipshare.platformUtils.AndroidUtils; 31 | 32 | public class InvisibleActivity extends Activity { 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | try { 37 | setContentView(R.layout.activity_invisible); 38 | WindowManager.LayoutParams params = getWindow().getAttributes(); 39 | params.dimAmount = 0; 40 | params.flags = 41 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 42 | | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 43 | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 44 | getWindow().setAttributes(params); 45 | AndroidUtils utils = new AndroidUtils(getApplicationContext(), this); 46 | BackgroundService.doUIOperation(utils); 47 | this.finish(); 48 | } catch (Exception ignored) { 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/platformUtils/DataContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.platformUtils; 26 | 27 | import androidx.annotation.Nullable; 28 | import java.io.File; 29 | import java.util.List; 30 | 31 | public class DataContainer { 32 | private Object data; 33 | private String message; 34 | 35 | public void setData(Object data) { 36 | this.data = data; 37 | } 38 | 39 | @Nullable 40 | public String getString() { 41 | if (data instanceof String) { 42 | return (String) data; 43 | } 44 | return null; 45 | } 46 | 47 | @Nullable 48 | public List getFiles() { 49 | if (data instanceof File file) { 50 | return List.of(file); 51 | } 52 | if (data instanceof List) { 53 | for (Object obj : (List) data) { 54 | if (!(obj instanceof File)) return null; 55 | } 56 | //noinspection unchecked 57 | return (List) data; 58 | } 59 | return null; 60 | } 61 | 62 | @Nullable 63 | public String getMessage() { 64 | return this.message; 65 | } 66 | 67 | public void setMessage(String msg) { 68 | this.message = msg; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/platformUtils/directoryTree/DirectoryTreeNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.platformUtils.directoryTree; 26 | 27 | import android.net.Uri; 28 | import java.util.LinkedList; 29 | 30 | public abstract class DirectoryTreeNode { 31 | public String name; 32 | private final Directory parent; 33 | 34 | DirectoryTreeNode(String name, Directory parent) { 35 | this.name = name; 36 | this.parent = parent; 37 | } 38 | 39 | public abstract int getLeafCount(boolean includeLeafDirs); 40 | 41 | public abstract long getFileSize(); 42 | 43 | public abstract Uri getUri(); 44 | 45 | public String getFullName() { 46 | LinkedList stack = new LinkedList<>(); 47 | DirectoryTreeNode node = this; 48 | do { 49 | stack.push(node); 50 | node = node.parent; 51 | } while (node != null); 52 | StringBuilder builder = new StringBuilder(); 53 | boolean first = true; 54 | while (!stack.isEmpty()) { 55 | if (!first) builder.append('/'); 56 | first = false; 57 | builder.append(stack.pop().name); 58 | } 59 | return builder.toString(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build-and-Test 2 | run-name: Build and Test on ${{ github.sha }} 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - 'app/**' 10 | - 'settings.gradle' 11 | - 'build.gradle' 12 | - '.github/workflows/build-test.yml' 13 | pull_request: 14 | branches: 15 | - master 16 | workflow_call: null 17 | 18 | jobs: 19 | Style-Check: 20 | uses: ./.github/workflows/check-style.yml 21 | with: 22 | trigger: "${{ github.event_name }}" 23 | permissions: 24 | contents: write 25 | pull-requests: write 26 | 27 | Build-and-Test: 28 | runs-on: ubuntu-latest 29 | timeout-minutes: 15 30 | needs: Style-Check 31 | 32 | steps: 33 | - name: Check out repository code 34 | uses: actions/checkout@v6 35 | with: 36 | ref: ${{ github.ref }} 37 | 38 | - name: Set up JDK 21 39 | uses: actions/setup-java@v5 40 | with: 41 | java-version: '21' 42 | distribution: 'temurin' 43 | 44 | - name: Create KeyStore and gradle.properties 45 | run: | 46 | storePass=dummyPassword 47 | keyAlias=test 48 | keyPass=dummyPassword 49 | keytool -genkey -keystore keyStore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias "$keyAlias" \ 50 | -dname "cn=Unknown, ou=Unknown, o=Unknown, c=Unknown" -storepass "$storePass" -keypass "$keyPass" 51 | mkdir -p ~/.gradle 52 | echo "RELEASE_STORE_FILE=../keyStore.jks" >~/.gradle/gradle.properties 53 | echo "RELEASE_STORE_PASSWORD=$storePass" >>~/.gradle/gradle.properties 54 | echo "RELEASE_KEY_ALIAS=$keyAlias" >>~/.gradle/gradle.properties 55 | echo "RELEASE_KEY_PASSWORD=$keyPass" >>~/.gradle/gradle.properties 56 | 57 | - name: Build 58 | run: | 59 | chmod +x gradlew 60 | ./gradlew assembleLatest 61 | 62 | - name: Enable KVM 63 | run: | 64 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | \ 65 | sudo tee /etc/udev/rules.d/99-kvm4all.rules 66 | sudo udevadm control --reload-rules 67 | sudo udevadm trigger --name-match=kvm 68 | 69 | - name: Test 70 | uses: reactivecircus/android-emulator-runner@v2 71 | with: 72 | api-level: 34 73 | arch: x86_64 74 | script: ./gradlew connectedLatestDebugAndroidTest 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/protocol/ProtoV1.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.protocol; 26 | 27 | import com.tw.clipshare.netConnection.ServerConnection; 28 | import com.tw.clipshare.platformUtils.AndroidUtils; 29 | import com.tw.clipshare.platformUtils.StatusNotifier; 30 | 31 | public class ProtoV1 extends Proto { 32 | 33 | ProtoV1(ServerConnection serverConnection, AndroidUtils utils, StatusNotifier notifier) { 34 | super(serverConnection, utils, notifier); 35 | } 36 | 37 | @Override 38 | public boolean getText() { 39 | return this.protoMethods.v1_getText(); 40 | } 41 | 42 | @Override 43 | public boolean sendText(String text) { 44 | return this.protoMethods.v1_sendText(text); 45 | } 46 | 47 | @Override 48 | public boolean getFile() { 49 | return this.protoMethods.v1_getFiles(); 50 | } 51 | 52 | @Override 53 | public boolean sendFile() { 54 | return this.protoMethods.v1_sendFile(); 55 | } 56 | 57 | @Override 58 | public boolean getImage() { 59 | return this.protoMethods.v1_getImage(); 60 | } 61 | 62 | @Override 63 | public String checkInfo() { 64 | return this.protoMethods.v1_checkInfo(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/protocol/ProtoV2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.protocol; 26 | 27 | import com.tw.clipshare.netConnection.ServerConnection; 28 | import com.tw.clipshare.platformUtils.AndroidUtils; 29 | import com.tw.clipshare.platformUtils.StatusNotifier; 30 | 31 | public class ProtoV2 extends Proto { 32 | 33 | ProtoV2(ServerConnection serverConnection, AndroidUtils utils, StatusNotifier notifier) { 34 | super(serverConnection, utils, notifier); 35 | } 36 | 37 | @Override 38 | public boolean getText() { 39 | return this.protoMethods.v1_getText(); 40 | } 41 | 42 | @Override 43 | public boolean sendText(String text) { 44 | return this.protoMethods.v1_sendText(text); 45 | } 46 | 47 | @Override 48 | public boolean getFile() { 49 | return this.protoMethods.v2_getFiles(); 50 | } 51 | 52 | @Override 53 | public boolean sendFile() { 54 | return this.protoMethods.v2_sendFiles(); 55 | } 56 | 57 | @Override 58 | public boolean getImage() { 59 | return this.protoMethods.v1_getImage(); 60 | } 61 | 62 | @Override 63 | public String checkInfo() { 64 | return this.protoMethods.v1_checkInfo(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'com.github.sherter.google-java-format' version '0.9' 4 | } 5 | 6 | android { 7 | compileSdkVersion = 34 8 | buildToolsVersion = "34.0.0" 9 | namespace "com.tw.clipshare" 10 | flavorDimensions = ["default"] 11 | 12 | defaultConfig { 13 | applicationId = "com.tw.clipshare" 14 | minSdkVersion 24 15 | targetSdkVersion 34 16 | versionCode = 31900 17 | versionName = "3.19.0" 18 | resConfigs "en" 19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | 22 | packagingOptions { 23 | dex { 24 | useLegacyPackaging true 25 | } 26 | } 27 | 28 | signingConfigs { 29 | release { 30 | storeFile file(RELEASE_STORE_FILE) 31 | storePassword RELEASE_STORE_PASSWORD 32 | keyAlias RELEASE_KEY_ALIAS 33 | keyPassword RELEASE_KEY_PASSWORD 34 | 35 | v2SigningEnabled true 36 | } 37 | } 38 | 39 | productFlavors { 40 | latest { 41 | minSdkVersion 28 42 | } 43 | legacy { 44 | minSdkVersion 24 45 | } 46 | } 47 | 48 | buildTypes { 49 | release { 50 | minifyEnabled = true 51 | shrinkResources = true 52 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 53 | signingConfig signingConfigs.release 54 | testCoverageEnabled = false 55 | } 56 | debug { 57 | minifyEnabled = false 58 | shrinkResources = false 59 | testCoverageEnabled = true 60 | } 61 | } 62 | 63 | compileOptions { 64 | sourceCompatibility JavaVersion.VERSION_17 65 | targetCompatibility JavaVersion.VERSION_17 66 | } 67 | } 68 | 69 | dependencies { 70 | implementation 'androidx.appcompat:appcompat:1.7.1' 71 | implementation 'com.google.android.material:material:1.13.0' 72 | implementation "androidx.documentfile:documentfile:1.1.0" 73 | implementation 'org.json:json:20250517' 74 | androidTestImplementation 'androidx.test.ext:junit:1.3.0' 75 | androidTestImplementation 'androidx.test:core:1.7.0' 76 | androidTestImplementation 'androidx.test:runner:1.7.0' 77 | androidTestImplementation 'androidx.test:rules:1.7.0' 78 | } 79 | 80 | googleJavaFormat { 81 | toolVersion = "1.25.2" 82 | exclude 'src/test' 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/protocol/ProtoV3.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.protocol; 26 | 27 | import com.tw.clipshare.netConnection.ServerConnection; 28 | import com.tw.clipshare.platformUtils.AndroidUtils; 29 | import com.tw.clipshare.platformUtils.StatusNotifier; 30 | 31 | public class ProtoV3 extends Proto { 32 | 33 | ProtoV3(ServerConnection serverConnection, AndroidUtils utils, StatusNotifier notifier) { 34 | super(serverConnection, utils, notifier); 35 | } 36 | 37 | @Override 38 | public boolean getText() { 39 | return this.protoMethods.v1_getText(); 40 | } 41 | 42 | @Override 43 | public boolean sendText(String text) { 44 | return this.protoMethods.v1_sendText(text); 45 | } 46 | 47 | @Override 48 | public boolean getFile() { 49 | return this.protoMethods.v3_getFiles(); 50 | } 51 | 52 | @Override 53 | public boolean sendFile() { 54 | return this.protoMethods.v3_sendFiles(); 55 | } 56 | 57 | @Override 58 | public boolean getImage() { 59 | return this.protoMethods.v1_getImage(); 60 | } 61 | 62 | public boolean getCopiedImage() { 63 | return this.protoMethods.v3_getCopiedImage(); 64 | } 65 | 66 | public boolean getScreenshot(int display) { 67 | return this.protoMethods.v3_getScreenshot(display); 68 | } 69 | 70 | @Override 71 | public String checkInfo() { 72 | return this.protoMethods.v1_checkInfo(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tw/clipshare/proto/BAOStreamBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.proto; 26 | 27 | import java.io.ByteArrayInputStream; 28 | import java.io.ByteArrayOutputStream; 29 | import java.io.IOException; 30 | import java.nio.charset.StandardCharsets; 31 | 32 | public class BAOStreamBuilder { 33 | private final ByteArrayOutputStream baoStream; 34 | 35 | BAOStreamBuilder() { 36 | this.baoStream = new ByteArrayOutputStream(); 37 | } 38 | 39 | public ByteArrayInputStream getStream() { 40 | return new ByteArrayInputStream(this.baoStream.toByteArray()); 41 | } 42 | 43 | public byte[] getArray() { 44 | return this.baoStream.toByteArray(); 45 | } 46 | 47 | public void addByte(int b) { 48 | this.baoStream.write(b); 49 | } 50 | 51 | public void addBytes(byte[] array) throws IOException { 52 | this.baoStream.write(array); 53 | } 54 | 55 | public void addSize(long size) throws IOException { 56 | byte[] data = new byte[8]; 57 | for (int i = data.length - 1; i >= 0; i--) { 58 | data[i] = (byte) (size & 0xFF); 59 | size >>= 8; 60 | } 61 | this.baoStream.write(data); 62 | } 63 | 64 | public void addString(String str) throws IOException { 65 | byte[] bytes = str.getBytes(StandardCharsets.UTF_8); 66 | addData(bytes); 67 | } 68 | 69 | public void addData(byte[] data) throws IOException { 70 | addSize(data.length); 71 | this.baoStream.write(data); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/protocol/Proto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.protocol; 26 | 27 | import com.tw.clipshare.netConnection.ServerConnection; 28 | import com.tw.clipshare.platformUtils.AndroidUtils; 29 | import com.tw.clipshare.platformUtils.DataContainer; 30 | import com.tw.clipshare.platformUtils.StatusNotifier; 31 | 32 | public abstract class Proto { 33 | protected final ProtoMethods protoMethods; 34 | public final DataContainer dataContainer; 35 | 36 | protected Proto(ServerConnection serverConnection, AndroidUtils utils, StatusNotifier notifier) { 37 | this.dataContainer = new DataContainer(); 38 | this.protoMethods = new ProtoMethods(serverConnection, utils, notifier, dataContainer); 39 | } 40 | 41 | public void setStatusNotifier(StatusNotifier notifier) { 42 | this.protoMethods.setStatusNotifier(notifier); 43 | } 44 | 45 | /** Close the connection used for communicating with the server */ 46 | public void close() { 47 | this.protoMethods.close(); 48 | } 49 | 50 | public abstract boolean getText(); 51 | 52 | public abstract boolean sendText(String text); 53 | 54 | public abstract boolean getFile(); 55 | 56 | public abstract boolean sendFile(); 57 | 58 | public abstract boolean getImage(); 59 | 60 | public abstract String checkInfo(); 61 | 62 | public void requestStop() { 63 | this.protoMethods.requestStop(); 64 | } 65 | 66 | public boolean isStopped() { 67 | return this.protoMethods.isStopped(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tw/clipshare/platformUtils/AndroidUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare.platformUtils; 26 | 27 | import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 28 | import static org.junit.Assert.*; 29 | 30 | import android.content.Context; 31 | import androidx.test.core.app.ActivityScenario; 32 | import androidx.test.ext.junit.runners.AndroidJUnit4; 33 | import com.tw.clipshare.ClipShareActivity; 34 | import org.junit.Test; 35 | import org.junit.runner.RunWith; 36 | 37 | @RunWith(AndroidJUnit4.class) 38 | public class AndroidUtilsTest { 39 | @Test 40 | public void testClipboardMethods() { 41 | String text = "Sample text for clipboard test testClipboardMethods"; 42 | try { 43 | ActivityScenario.launch(ClipShareActivity.class) 44 | .onActivity( 45 | activity -> { 46 | Context appContext = activity.getApplicationContext(); 47 | AndroidUtils androidUtils = new AndroidUtils(appContext, activity); 48 | 49 | androidUtils.setClipboardText(text); 50 | 51 | String received = androidUtils.getClipboardText(); 52 | assertEquals(text, received); 53 | }) 54 | .close(); 55 | } catch (Exception ignored) { 56 | } 57 | } 58 | 59 | @Test 60 | public void testClipboardMethodsNoActivity() { 61 | try { 62 | Context appContext = getInstrumentation().getTargetContext(); 63 | AndroidUtils androidUtils = new AndroidUtils(appContext, null); 64 | 65 | String received = androidUtils.getClipboardText(); 66 | assertNull(received); 67 | } catch (Exception ignored) { 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/check-style.yml: -------------------------------------------------------------------------------- 1 | name: Style-Check 2 | run-name: Check Style on ${{ github.sha }} 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - '.github/**' 10 | - '!.github/workflows/resources/*' 11 | - '!.github/workflows/build-test.yml' 12 | - '.yamllint' 13 | workflow_call: 14 | inputs: 15 | trigger: 16 | type: string 17 | required: false 18 | 19 | jobs: 20 | Style-Check: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 5 23 | 24 | permissions: 25 | contents: write 26 | pull-requests: write 27 | 28 | steps: 29 | - name: Check out repository code 30 | uses: actions/checkout@v6 31 | with: 32 | ref: ${{ github.ref }} 33 | 34 | - name: Install tools 35 | run: | 36 | echo "set man-db/auto-update false" | sudo debconf-communicate && sudo dpkg-reconfigure man-db -f noninteractive 37 | sudo apt-get update && sudo apt-get install -y --no-install-recommends yamllint 38 | 39 | - name: Set up JDK 21 40 | uses: actions/setup-java@v5 41 | with: 42 | java-version: '21' 43 | distribution: 'temurin' 44 | 45 | - name: Set environment 46 | run: echo EVENT="${{ inputs.trigger || github.event_name }}" >> $GITHUB_ENV 47 | 48 | - name: Create gradle.properties 49 | run: | 50 | mkdir -p ~/.gradle 51 | echo "RELEASE_STORE_FILE=notExisting.jks" >~/.gradle/gradle.properties 52 | echo "RELEASE_STORE_PASSWORD=unused" >>~/.gradle/gradle.properties 53 | echo "RELEASE_KEY_ALIAS=unused" >>~/.gradle/gradle.properties 54 | echo "RELEASE_KEY_PASSWORD=unused" >>~/.gradle/gradle.properties 55 | 56 | - name: Grant execute permission for gradlew 57 | run: chmod +x gradlew 58 | 59 | - name: Verify Google Java format 60 | run: ./gradlew verifyGoogleJavaFormat 61 | 62 | - name: Check yaml style 63 | run: yamllint . 64 | 65 | - name: Format Java code 66 | if: ${{ (env.EVENT == 'push') && (success() || failure()) }} 67 | run: ./gradlew googleJavaFormat 68 | 69 | - name: Create pull request 70 | if: ${{ (env.EVENT == 'push') && (success() || failure()) }} 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | run: | 74 | [[ -z "$(git status -s)" ]] && echo No changes && exit 0 75 | curr_branch="${{ github.ref_name }}" 76 | new_branch="auto-format-$(git rev-parse HEAD | head -c 8)" 77 | author_name="$(git log -1 --pretty=format:'%an')" 78 | author_email="$(git log -1 --pretty=format:'%ae')" 79 | git checkout -b "$new_branch" && git merge "$curr_branch" 80 | git config user.name "$author_name" 81 | git config user.email "$author_email" 82 | git remote set-url origin "https://github.com/${{ github.repository }}" 83 | git commit -am 'Apply code formatting automatically from GitHub actions' 84 | git push origin "$new_branch" 85 | gh pr create -B "$curr_branch" -H "$new_branch" --title "Merge \`$new_branch\` into \`$curr_branch\`" \ 86 | --body 'Apply code formatting [generated automatically]' 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/tw/clipshare/CertUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022-2025 H. Thevindu J. Wijesekera 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.tw.clipshare; 26 | 27 | import java.io.InputStream; 28 | import java.security.KeyStore; 29 | import java.security.cert.CertificateFactory; 30 | import java.security.cert.X509Certificate; 31 | import java.util.Enumeration; 32 | import javax.net.ssl.KeyManagerFactory; 33 | 34 | public class CertUtils { 35 | public static String getCertCN(X509Certificate cert) { 36 | try { 37 | String name = cert.getSubjectX500Principal().getName("RFC1779"); 38 | String[] attributes = name.split(","); 39 | String cn = null; 40 | for (String attribute : attributes) { 41 | if (!attribute.startsWith("CN=")) { 42 | continue; 43 | } 44 | String[] cnSep = attribute.split("=", 2); 45 | cn = cnSep[1]; 46 | break; 47 | } 48 | return cn; 49 | } catch (Exception ignored) { 50 | return null; 51 | } 52 | } 53 | 54 | public static String getCertCN(char[] passwd, InputStream certIn) { 55 | try { 56 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 57 | keyStore.load(certIn, passwd); 58 | KeyManagerFactory kmf = 59 | KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 60 | kmf.init(keyStore, passwd); 61 | Enumeration enm = keyStore.aliases(); 62 | if (enm.hasMoreElements()) { 63 | String alias = enm.nextElement(); 64 | X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); 65 | return getCertCN(cert); 66 | } 67 | return null; 68 | } catch (Exception ignored) { 69 | return null; 70 | } 71 | } 72 | 73 | public static X509Certificate getX509fromInputStream(InputStream caCertIn) { 74 | try { 75 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); 76 | return (X509Certificate) cf.generateCertificate(caCertIn); 77 | } catch (Exception ignored) { 78 | return null; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create-Release 2 | run-name: Create release ${{github.ref_name}} 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v[0-9]+.[0-9]+.[0-9]+' 8 | 9 | jobs: 10 | Build-and-Test: 11 | uses: ./.github/workflows/build-test.yml 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | security-events: write 16 | 17 | Release: 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 20 20 | permissions: 21 | contents: write 22 | id-token: write 23 | attestations: write 24 | needs: 25 | - Build-and-Test 26 | 27 | steps: 28 | - name: Check out repository code 29 | uses: actions/checkout@v6 30 | 31 | - name: Set variables 32 | run: | 33 | version="$(echo '${{github.ref_name}}' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" 34 | echo VERSION="$version" >> $GITHUB_ENV 35 | echo TAG="v${version}" >> $GITHUB_ENV 36 | echo TITLE="Version ${version}" >> $GITHUB_ENV 37 | 38 | - name: Check version 39 | run: | 40 | version="${{env.VERSION}}" 41 | echo 'Checking versionName ...' 42 | [ ! -z "$(grep -E 'versionName\s?=' app/build.gradle | grep -F "$version")" ] 43 | echo 'versionName is correct.' 44 | major="$(cut -d'.' -f1 <<<"$version")" 45 | minor="$(cut -d'.' -f2 <<<"$version")" 46 | patch="$(cut -d'.' -f3 <<<"$version")" 47 | versionCode="$(printf '%i%02i%02i' "$major" "$minor" "$patch")" 48 | echo 'Checking versionCode ...' 49 | [ ! -z "$(grep -E 'versionCode\s?=' app/build.gradle | grep -F "$versionCode")" ] 50 | echo 'versionCode is correct.' 51 | 52 | - name: Set up JDK 21 53 | uses: actions/setup-java@v5 54 | with: 55 | java-version: '21' 56 | distribution: 'temurin' 57 | 58 | - name: Set gradle.properties 59 | env: 60 | PROPERTIES_BASE64: ${{ secrets.PROPERTIES_BASE64 }} 61 | run: | 62 | mkdir -p ~/.gradle 63 | echo "$PROPERTIES_BASE64" | base64 --decode >~/.gradle/gradle.properties 64 | 65 | - name: Set KeyStore 66 | env: 67 | KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} 68 | run: echo "$KEYSTORE_BASE64" | base64 --decode >keyStore.jks 69 | 70 | - name: Build APK 71 | run: | 72 | chmod +x gradlew 73 | ./gradlew assembleRelease 74 | mkdir release 75 | mv app/build/outputs/apk/latest/release/*.apk "release/clip_share_client.apk" 76 | mv app/build/outputs/apk/legacy/release/*.apk "release/clip_share_client-legacy.apk" 77 | cd release/ 78 | sha256sum -b * >SHA2-256SUM 79 | 80 | - name: Generate artifact attestation 81 | uses: actions/attest-build-provenance@v3 82 | with: 83 | subject-path: "release/SHA2-256SUM" 84 | 85 | - name: Create release 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 88 | run: | 89 | sed -i "s//${{env.VERSION}}/g" .github/workflows/resources/release_notes.md 90 | cd release 91 | gh release create "${{env.TAG}}" --latest --verify-tag \ 92 | --notes-file ../.github/workflows/resources/release_notes.md \ 93 | --title "${{env.TITLE}}" * 94 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popup_reset.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 23 | 24 | 28 | 29 | 36 | 37 | 41 | 42 | 43 | 48 | 49 | 53 | 54 |