Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
target
# Maven
target/

# Gradle
.gradle/
build/
gradle/
gradlew
gradlew.bat
local.properties

# IDE
.idea/
*.iml

# Build outputs
*.apk
*.ap_
*.dex

# Temp files
*.class
*.log
*.tmp
*.temp
nul
tmpclaude-*

# OS
.DS_Store
Thumbs.db

# ADBKeyBoard (separate project)
ADBKeyBoard/
60 changes: 60 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
}
}

allprojects {
repositories {
google()
mavenCentral()
}
}

apply plugin: 'com.android.application'

android {
namespace 'ca.zgrs.clipper'
compileSdk 34

defaultConfig {
applicationId "ca.zgrs.clipper"
minSdk 21
targetSdk 34
versionCode 3
versionName "2.0"
}

buildTypes {
release {
minifyEnabled false
}
debug {
minifyEnabled false
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
res.srcDirs = ['src/main/res']
}
}

lint {
abortOnError false
}
}

dependencies {
}
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
android.useAndroidX=false
android.enableJetifier=false
org.gradle.jvmargs=-Xmx1536m
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<name>clipboard-service</name>

<properties>
<platform.version>2.3.3</platform.version>
<android.sdk.version>10</android.sdk.version>
<platform.version>13</platform.version>
<android.sdk.version>33</android.sdk.version>
<android.sdk.path>${env.ANDROID_HOME}</android.sdk.path>
</properties>

Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'Clipper'
45 changes: 24 additions & 21 deletions src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ca.zgrs.clipper" android:versionCode="1" android:versionName="1.1" android:enabled="true">
android:versionCode="3" android:versionName="2.0" android:enabled="true">

<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Main" android:launchMode="singleInstance">
<activity android:name=".Main"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="ca.zgrs.clipper.GET" />
<action android:name="ca.zgrs.clipper.SET" />
<action android:name="clipper.get" />
<action android:name="clipper.set" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service android:name=".ClipboardService"
android:exported="true"
android:enabled="true">
</service>
<receiver android:name=".ClipperReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<data android:scheme="clipper" />
</intent-filter>
<intent-filter>
<action android:name="clipper.get" />
<action android:name="clipper.set" />
<action android:name="get" />
<action android:name="set" />
</intent-filter>
</receiver>

<service android:name=".ClipboardService"
android:exported="false"
android:enabled="true"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="clipboard_access" />
</service>

</application>

</manifest>

84 changes: 64 additions & 20 deletions src/main/java/ca/zgrs/clipper/ClipboardService.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,82 @@
package ca.zgrs.clipper;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;

import android.os.Build;
import android.os.IBinder;
import android.util.Log;

public class ClipboardService extends IntentService {
private static String TAG = "ClipboardService";
public class ClipboardService extends Service {
private static final String TAG = "ClipboardService";
private static final String CHANNEL_ID = "clipper_service_channel";
private static final int NOTIFICATION_ID = 1;

public ClipboardService() {
super("ClipboardService");
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "ClipboardService created");
createNotificationChannel();
}

/* Define service as sticky so that it stays in background */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Log.d(TAG, "ClipboardService started");
startForeground(NOTIFICATION_ID, createNotification());
return START_STICKY;
}

@Override
public void onCreate() {
super.onCreate();
// start itself to ensure our broadcast receiver is active
Log.d(TAG, "Start clipboard service.");
startService(new Intent(getApplicationContext(), ClipboardService.class));
public IBinder onBind(Intent intent) {
return null;
}

/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
public void onDestroy() {
Log.d(TAG, "ClipboardService destroyed");
super.onDestroy();
}

private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Clipper Service",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("Keeps Clipper running for clipboard access");
channel.setShowBadge(false);

NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
}

private Notification createNotification() {
Intent notificationIntent = new Intent(this, Main.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE
);

Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(this, CHANNEL_ID);
} else {
builder = new Notification.Builder(this);
}

return builder
.setContentTitle("Clipper")
.setContentText("Clipboard service is running")
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setOngoing(true)
.build();
}
}
}
22 changes: 15 additions & 7 deletions src/main/java/ca/zgrs/clipper/ClipperReceiver.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package ca.zgrs.clipper;

import android.app.Activity;
import android.text.ClipboardManager;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.BroadcastReceiver;
Expand Down Expand Up @@ -36,7 +37,8 @@ public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Setting text into clipboard");
String text = intent.getStringExtra(EXTRA_TEXT);
if (text != null) {
cb.setText(text);
ClipData clip = ClipData.newPlainText("Clipper", text);
cb.setPrimaryClip(clip);
setResultCode(Activity.RESULT_OK);
setResultData("Text is copied into clipboard.");
} else {
Expand All @@ -45,11 +47,17 @@ public void onReceive(Context context, Intent intent) {
}
} else if (isActionGet(intent.getAction())) {
Log.d(TAG, "Getting text from clipboard");
CharSequence clip = cb.getText();
if (clip != null) {
Log.d(TAG, String.format("Clipboard text: %s", clip));
setResultCode(Activity.RESULT_OK);
setResultData(clip.toString());
ClipData clipData = cb.getPrimaryClip();
if (clipData != null && clipData.getItemCount() > 0) {
CharSequence clip = clipData.getItemAt(0).getText();
if (clip != null) {
Log.d(TAG, String.format("Clipboard text: %s", clip));
setResultCode(Activity.RESULT_OK);
setResultData(clip.toString());
} else {
setResultCode(Activity.RESULT_CANCELED);
setResultData("");
}
} else {
setResultCode(Activity.RESULT_CANCELED);
setResultData("");
Expand Down
Loading