Skip to content

Instantly share code, notes, and snippets.

@vadiole
Created September 30, 2021 00:12
Show Gist options
  • Save vadiole/64cbcf41f123808afb71d6dc352634cf to your computer and use it in GitHub Desktop.
Save vadiole/64cbcf41f123808afb71d6dc352634cf to your computer and use it in GitHub Desktop.
WallpaperService with View
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Canvas
import android.os.PowerManager
import android.service.wallpaper.WallpaperService
import android.util.Log
import android.view.Choreographer
import android.view.SurfaceHolder
import android.view.View
/**
* WallpaperService adapted for use with View.
* Its only purpose is to generate a ViewEngine if necessary
*
* Pass in < > the type of your view to be displayed on the wallpaper.
*
* Example:
* class RainWallpaperService : ViewWallpaperService<RainView>()
*/
abstract class ViewWallpaperService<T : View> : WallpaperService() {
/**
* Must be implemented to return a **NEW instance** of the [ViewEngine]
*/
abstract override fun onCreateEngine(): ViewEngine
/**
* This abstract [ViewEngine] will take care of measuring, layouting and drawing
* your [wallpaperView] on the wallpaper canvas.
*
* You only need to override the [wallpaperView]
*/
abstract inner class ViewEngine : Engine(), Choreographer.FrameCallback {
/**
* Wallpaper view. It will be measured, layouted and drawn on wallpaper canvas.
*
* You must provide a **NEW instance** of [wallpaperView] for each [ViewEngine]
*/
abstract val wallpaperView: T
/**
* Draws [wallpaperView] on the [canvas].
* Consider override this to draw your view manually depending on [frameTimeNanos].
*
* **Do not call super if you override this method!**
*/
open fun doDraw(canvas: Canvas, frameTimeNanos: Long) {
wallpaperView.draw(canvas)
}
/**
* Power manager, used to check if the screen is on when the visibility of the wallpaper changes
*/
private val powerManager = getSystemService(PowerManager::class.java)
/**
* Just a flag that says whether the [wallpaperView] is drawn at the moment
*/
private var isRunning = false
/**
* This receiver is needed to start and stop drawing when user turns the screen on and off
*/
private val screenReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val isInteractive = when (intent.action) {
Intent.ACTION_SCREEN_ON -> true
Intent.ACTION_SCREEN_OFF -> false
else -> throw IllegalArgumentException()
}
performVisibilityChanged(isVisible, isInteractive)
}
}
/**
* When the [ViewEngine] is created we register a receiver
* to receive events about the screen turning on and off
*/
override fun onCreate(surfaceHolder: SurfaceHolder) {
val intentFilter = IntentFilter(Intent.ACTION_SCREEN_ON)
intentFilter.addAction(Intent.ACTION_SCREEN_OFF)
registerReceiver(screenReceiver, intentFilter)
}
/**
* And unregister when [ViewEngine] is destroyed
*/
override fun onDestroy() {
unregisterReceiver(screenReceiver)
}
/**
* When the size of the surface has changed, we measure and layout the [wallpaperView]
*/
override fun onSurfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
Log.d("ViewWallpaperEngine", "onSurfaceChanged: $width, $height")
val widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
val heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
wallpaperView.measure(widthSpec, heightSpec)
wallpaperView.layout(0, 0, width, height)
}
/**
* When the visibility of the wallpaper has changed,
* we decide whether it is necessary to draw, considering whether the screen is on
*/
override fun onVisibilityChanged(visible: Boolean) {
performVisibilityChanged(visible, powerManager.isInteractive)
}
/**
* Called by Choreographer when a new display frame is being rendered.
* This method calls the [doDraw] method and posts the next frame callback
*/
override fun doFrame(frameTimeNanos: Long) {
if (isRunning) {
var canvas: Canvas? = null
try {
canvas = surfaceHolder.lockCanvas()
doDraw(canvas, frameTimeNanos)
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas)
}
}
Choreographer.getInstance().postFrameCallback(this)
}
}
/**
* This is where changes in wallpaper visibility and screen state are handled.
* If the wallpaper is visible and the screen is on - we post frame callback, otherwise remove it
*/
private fun performVisibilityChanged(isVisible: Boolean, isInteractive: Boolean) {
val shouldRun = isVisible && isInteractive
if (isRunning != shouldRun) {
isRunning = shouldRun
Log.d("ViewWallpaperEngine", "isRunning: $isRunning")
if (shouldRun) {
Choreographer.getInstance().postFrameCallback(this)
} else {
Choreographer.getInstance().removeFrameCallback(this)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment