Skip to content

Instantly share code, notes, and snippets.

@m7mdra
Created October 6, 2019 11:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m7mdra/b7c4fa5b5ca2e535baa2e59a5c3b3daa to your computer and use it in GitHub Desktop.
Save m7mdra/b7c4fa5b5ca2e535baa2e59a5c3b3daa to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.m7mdra.copythat">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".clipboard.ClipBoardService"
android:enabled="true"
android:exported="true"
>
</service>
<receiver
android:name=".clipboard.BootReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<activity android:name=".ui.main.MainActivity"
android:launchMode="singleInstance"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
/*
* Copyright (c) 2019.
* ______ _
* |____ | | |
* _ __ ___ / /_ __ ___ __| | _ __ __ _
* | '_ ` _ \ / /| '_ ` _ \ / _` || '__|/ _` |
* | | | | | | / / | | | | | || (_| || | | (_| |
* |_| |_| |_|/_/ |_| |_| |_| \__,_||_| \__,_|
*
*
*/
package com.m7mdra.copythat.clipboard
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
class BootReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent) {
val intent = Intent(context, ClipBoardService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}
/*
* Copyright (c) 2019.
* ______ _
* |____ | | |
* _ __ ___ / /_ __ ___ __| | _ __ __ _
* | '_ ` _ \ / /| '_ ` _ \ / _` || '__|/ _` |
* | | | | | | / / | | | | | || (_| || | | (_| |
* |_| |_| |_|/_/ |_| |_| |_| \__,_||_| \__,_|
*
*
*/
package com.m7mdra.copythat.clipboard
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import com.m7mdra.copythat.database.ClipDatabase
import com.m7mdra.copythat.database.ClipEntry
import com.m7mdra.copythat.dispose
import com.m7mdra.copythat.ioMainTransformer
import com.m7mdra.copythat.log
import io.reactivex.disposables.Disposable
class ClipBoard(private val context: Context, private val database: ClipDatabase) :
ClipboardManager.OnPrimaryClipChangedListener {
private val disposables = mutableListOf<Disposable>()
override fun onPrimaryClipChanged() {
val primaryClip = clipboardManager.primaryClip
if (primaryClip != null) {
val description = primaryClip.description
val item = primaryClip.getItemAt(0)
val htmlText = item.htmlText
val text = item.text
val mimeType = description.getMimeType(0)
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
description.timestamp
} else {
System.currentTimeMillis()
}
val clipEntry = ClipEntry(data = text?.toString() ?: htmlText, date = date, mimeType = mimeType)
insertToDb(clipEntry)
}
}
private fun insertToDb(clipEntry: ClipEntry) {
disposables + database.dao().insert(clipEntry)
.compose(ioMainTransformer())
.subscribe({
"inserted item $clipEntry to database successfully".log()
}, { it.log() })
}
private var clipboardManager: ClipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
fun start() {
clipboardManager.addPrimaryClipChangedListener(this)
}
fun stop() {
disposables.dispose()
clipboardManager.removePrimaryClipChangedListener(this)
}
}
/*
* Copyright (c) 2019.
* ______ _
* |____ | | |
* _ __ ___ / /_ __ ___ __| | _ __ __ _
* | '_ ` _ \ / /| '_ ` _ \ / _` || '__|/ _` |
* | | | | | | / / | | | | | || (_| || | | (_| |
* |_| |_| |_|/_/ |_| |_| |_| \__,_||_| \__,_|
*
*
*/
package com.m7mdra.copythat.clipboard
import android.app.Service
import android.content.*
import android.os.IBinder
import com.m7mdra.copythat.ACTION_STOP_SERVICE
import com.m7mdra.copythat.NOTIFICATION_ID
import com.m7mdra.copythat.log
import org.koin.android.ext.android.inject
class ClipBoardService : Service() {
private val clipBoard: ClipBoard by inject()
private val clipNotification: ClipNotification by inject()
companion object {
var isActive = false
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
"Service Created".log()
isActive = true
clipBoard.start()
}
override fun onDestroy() {
clipBoard.stop()
isActive = false
super.onDestroy()
"Service stopped".log()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
if (intent.action == ACTION_STOP_SERVICE) {
stopForeground(true)
stopSelf()
return super.onStartCommand(intent, flags, startId)
} else {
startForeground(NOTIFICATION_ID, clipNotification.builder.build())
return START_STICKY
}
}
}
/*
* Copyright (c) 2019.
* ______ _
* |____ | | |
* _ __ ___ / /_ __ ___ __| | _ __ __ _
* | '_ ` _ \ / /| '_ ` _ \ / _` || '__|/ _` |
* | | | | | | / / | | | | | || (_| || | | (_| |
* |_| |_| |_|/_/ |_| |_| |_| \__,_||_| \__,_|
*
*
*/
package com.m7mdra.copythat.clipboard
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.m7mdra.copythat.*
import com.m7mdra.copythat.ui.main.MainActivity
class ClipNotification(private val context: Context) {
var builder: NotificationCompat.Builder
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
}
builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentTitle("CopyThat is running")
.setContentText("CopyThat will keep track of you clipboard actions and save them.")
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentIntent(getContentIntent())
.setSmallIcon(R.drawable.ic_typewriter_with_paper)
.addAction(NotificationCompat.Action(R.drawable.ic_stop_black_24dp, "Stop", getStopPendingIntent()))
}
private fun getContentIntent(): PendingIntent {
return PendingIntent.getActivity(context, 92, Intent(context, MainActivity::class.java)
, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getStopPendingIntent(): PendingIntent {
val intent = Intent(context, ClipBoardService::class.java)
intent.action = ACTION_STOP_SERVICE
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PendingIntent.getForegroundService(
context, REQUEST_CODE_STOP_SERVICE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getService(
context, REQUEST_CODE_STOP_SERVICE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"CopyThat service channel",
NotificationManager.IMPORTANCE_LOW
)
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment