Commit 10833792 authored by Amit Kumar's avatar Amit Kumar 💻

Improve blur behaviour and make navigation bar transparent

1. Use cached blur wallpaper instead of live blurring for smooth visual effects.
2. Make Navigation Bar transaprent to match with bottom dock.
3. Use transparent grey color as background for folder icons.
4. Use placeholder in case wallpaper is not available.
parent 37e61f24
Pipeline #32038 passed with stage
in 7 minutes
......@@ -6,6 +6,7 @@ import android.content.Context;
import foundation.e.blisslauncher.core.DeviceProfile;
import foundation.e.blisslauncher.core.IconsHandler;
import foundation.e.blisslauncher.core.blur.BlurWallpaperProvider;
import foundation.e.blisslauncher.core.customviews.WidgetHost;
import foundation.e.blisslauncher.features.launcher.AppProvider;
import io.github.inflationx.calligraphy3.CalligraphyConfig;
......@@ -21,8 +22,6 @@ public class BlissLauncher extends Application {
private static WidgetHost sAppWidgetHost;
private static AppWidgetManager sAppWidgetManager;
private static final String TAG = "BlissLauncher";
@Override
public void onCreate() {
super.onCreate();
......@@ -39,6 +38,9 @@ public class BlissLauncher extends Application {
sAppWidgetHost = new WidgetHost(getApplicationContext(),
R.id.APPWIDGET_HOST_ID);
sAppWidgetHost.startListening();
connectAppProvider();
BlurWallpaperProvider.Companion.getInstance(this);
}
public static BlissLauncher getApplication(Context context) {
......
package foundation.e.blisslauncher.core
import android.os.Handler
import android.os.Looper
val mainHandler by lazy { Handler(Looper.getMainLooper()) }
fun runOnMainThread(r: () -> Unit) {
runOnThread(mainHandler, r)
}
fun runOnThread(handler: Handler, r: () -> Unit) {
if (handler.looper.thread.id == Looper.myLooper()?.thread?.id) {
r()
} else {
handler.post(r)
}
}
inline fun <T> Iterable<T>.safeForEach(action: (T) -> Unit) {
val tmp = ArrayList<T>()
tmp.addAll(this)
for (element in tmp) action(element)
}
\ No newline at end of file
......@@ -19,6 +19,10 @@ import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -44,6 +48,19 @@ public class Utilities {
public static final boolean ATLEAST_MARSHMALLOW =
Build.VERSION.SDK_INT >= 23;
// These values are same as that in {@link AsyncTask}.
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
/**
* An {@link Executor} to be used with async task with no limit on the queue size.
*/
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
/**
* Compresses the bitmap to a byte array for serialization.
*/
......@@ -212,4 +229,6 @@ public class Utilities {
return defaultValue;
}
}
package foundation.e.blisslauncher.core.blur
import android.content.Context
import android.graphics.Bitmap
import com.hoko.blur.HokoBlur
import com.hoko.blur.task.AsyncBlurTask
class BlurWallpaperFilter(private val context: Context) : WallpaperFilter {
private var blurRadius = 8
override fun apply(wallpaper: Bitmap): WallpaperFilter.ApplyTask {
return WallpaperFilter.ApplyTask.create { emitter ->
HokoBlur.with(context)
.scheme(HokoBlur.SCHEME_RENDER_SCRIPT)
.mode(HokoBlur.MODE_STACK)
.radius(blurRadius)
.sampleFactor(8f)
.forceCopy(false)
.needUpscale(true)
.processor()
.asyncBlur(wallpaper, object : AsyncBlurTask.Callback {
override fun onBlurSuccess(bitmap: Bitmap) {
emitter.onSuccess(bitmap)
}
override fun onBlurFailed(error: Throwable?) {
emitter.onError(error!!)
}
})
}
}
}
\ No newline at end of file
package foundation.e.blisslauncher.core.blur;
import android.Manifest;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.support.v4.app.ActivityCompat;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;
import com.hoko.blur.HokoBlur;
import com.hoko.blur.processor.BlurProcessor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import foundation.e.blisslauncher.BlissLauncher;
import foundation.e.blisslauncher.core.Utilities;
public class BlurWallpaperProvider {
private final Context context;
private final WallpaperManager wallpaperManager;
private final BlurProcessor blurProcessor;
private ExecutorService mDispatcher = Executors.newSingleThreadExecutor();
private final Runnable updateRunnable = () -> updateWallpaper();
private DisplayMetrics displayMetrics = new DisplayMetrics();
private static final float SAMPLE_FACTOR = 8.0f;
private static BlurWallpaperProvider sInstance;
private Bitmap wallpaper;
private volatile Future mFuture;
private Listener listener;
private BlurWallpaperProvider(Context context) {
this.context = context;
this.wallpaperManager = WallpaperManager.getInstance(context);
blurProcessor = HokoBlur.with(context).sampleFactor(SAMPLE_FACTOR)
.scheme(HokoBlur.SCHEME_OPENGL)
.mode(HokoBlur.MODE_STACK)
.forceCopy(false)
.needUpscale(true)
.processor();
init();
listener = (Listener) context;
}
public static BlurWallpaperProvider getInstance(Context context) {
if (sInstance == null) {
sInstance = new BlurWallpaperProvider(context);
}
return sInstance;
}
private void init() {
updateWallpaper();
}
private void updateWallpaper() {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
return;
}
if (wallpaperManager.getWallpaperInfo() != null) {
// Wallpaper is live wallpaper so can't support blur effect.
return;
}
wallpaper = Utilities.drawableToBitmap(wallpaperManager.getDrawable(), true);
wallpaper = scaleAndCropToScreenSize(wallpaper);
}
public void blur(int radius) {
cancelPreTask(false);
mFuture = mDispatcher.submit(new BlurTask(wallpaper, blurProcessor, radius) {
@Override
void onBlurSuccess(Bitmap bitmap) {
if (bitmap != null && listener != null) {
listener.blurBackgroundLayer(bitmap);
}
}
@Override
void onBlurFailed(float factor) {
listener.fallbackToDimBackground(factor);
}
});
}
public Bitmap mergeLauncherView(Bitmap launcherView) {
updateWallpaper();
if (wallpaper == null) { // possibly we don't have access to read the wallpaper or the wallpaper is live wallpaper.
return null;
}
int wallpaperWidth = wallpaper.getWidth();
int wallpaperHeight = wallpaper.getHeight();
int overlayWidth = launcherView.getWidth();
int overlayHeight = launcherView.getHeight();
// Hack for removing soft navigation bar
if(overlayHeight > wallpaperHeight) {
overlayHeight = wallpaperHeight;
launcherView = Bitmap.createBitmap(launcherView, 0, 0, overlayWidth, overlayHeight);
}
float marginLeft = (float) (wallpaperWidth * 0.5 - overlayWidth * 0.5);
float marginTop = (float) (wallpaperHeight * 0.5 - overlayHeight * 0.5);
Bitmap finalBitmap = Bitmap.createBitmap(wallpaperWidth, wallpaperHeight, wallpaper.getConfig());
Canvas canvas = new Canvas(finalBitmap);
canvas.drawBitmap(wallpaper, new Matrix(), null);
canvas.drawBitmap(launcherView, marginLeft, marginTop, null);
return finalBitmap;
}
public void blurWithLauncherView(Bitmap view, int radius) {
cancelPreTask(false);
mFuture = mDispatcher.submit(new BlurTask(view, blurProcessor, radius) {
@Override
void onBlurSuccess(Bitmap bitmap) {
if (bitmap != null && listener != null) {
listener.blurFrontLayer(bitmap);
}
}
@Override
void onBlurFailed(float factor) {
listener.fallbackToDimBackground(factor);
}
});
}
public void cancelPreTask(boolean interrupt) {
if (mFuture != null && !mFuture.isCancelled() && !mFuture.isDone()) {
mFuture.cancel(interrupt);
mFuture = null;
}
}
private Bitmap scaleAndCropToScreenSize(Bitmap wallpaper) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
display.getRealMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
float widthFactor = ((float) width) / wallpaper.getWidth();
float heightFactor = ((float) height) / wallpaper.getHeight();
float upscaleFactor = Math.max(widthFactor, heightFactor);
if (upscaleFactor <= 0) {
return wallpaper;
}
int scaledWidth = (int) Math.max(width, (wallpaper.getWidth() * upscaleFactor));
int scaledHeight = (int) Math.max(height, (wallpaper.getHeight() * upscaleFactor));
wallpaper = Bitmap.createScaledBitmap(wallpaper, scaledWidth, scaledHeight, false);
int navigationBarHeight = 0;
if (BlissLauncher.getApplication(context).getDeviceProfile().hasSoftNavigationBar(context)) {
int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navigationBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
}
int y;
if (wallpaper.getHeight() > height) {
y = (wallpaper.getHeight() - height) / 2;
} else y = 0;
return Bitmap.createBitmap(wallpaper, 0, y, width, height - navigationBarHeight);
}
public interface Listener {
void blurBackgroundLayer(Bitmap bitmap);
void blurFrontLayer(Bitmap bitmap);
void fallbackToDimBackground(float dimAlpha);
}
public void clear() {
listener = null;
cancelPreTask(true);
sInstance = null;
}
private abstract static class BlurTask implements Runnable {
private Bitmap bitmap;
private BlurProcessor blurProcessor;
private int radius;
BlurTask(Bitmap bitmap, BlurProcessor blurProcessor, int radius) {
this.bitmap = bitmap;
this.blurProcessor = blurProcessor;
this.radius = radius;
}
@Override
public void run() {
if (bitmap != null && !bitmap.isRecycled() && blurProcessor != null) {
blurProcessor.radius(radius);
onBlurSuccess(blurProcessor.blur(bitmap));
} else {
onBlurFailed((float) radius / 15);
}
}
abstract void onBlurSuccess(Bitmap bitmap);
abstract void onBlurFailed(float factor);
}
}
package foundation.e.blisslauncher.core.blur
import android.Manifest
import android.app.WallpaperManager
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.os.Build
import android.support.v4.app.ActivityCompat
import android.util.DisplayMetrics
import android.util.Log
import android.view.WindowManager
import android.widget.Toast
import foundation.e.blisslauncher.core.Utilities
import foundation.e.blisslauncher.core.runOnMainThread
import foundation.e.blisslauncher.core.safeForEach
import foundation.e.blisslauncher.core.utils.SingletonHolder
import foundation.e.blisslauncher.core.utils.ensureOnMainThread
import foundation.e.blisslauncher.core.utils.useApplicationContext
import java.util.*
class BlurWallpaperProvider(val context: Context) {
private val wallpaperManager: WallpaperManager = WallpaperManager.getInstance(context)
private val listeners = ArrayList<Listener>()
private val displayMetrics = DisplayMetrics()
var wallpaper: Bitmap? = null
private set(value) {
if (field != value) {
field?.recycle()
field = value
}
}
var placeholder: Bitmap? = null
private set(value) {
if (field != value) {
field?.recycle()
field = value
}
}
private val notifyRunnable = Runnable {
for (listener in listeners) {
listener.onWallpaperChanged()
}
}
private val vibrancyPaint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG)
private val mUpdateRunnable = Runnable { updateWallpaper() }
private val wallpaperFilter = BlurWallpaperFilter(context)
private var applyTask: WallpaperFilter.ApplyTask? = null
private var updatePending = false
init {
isEnabled = getEnabledStatus()
updateAsync()
}
private fun getEnabledStatus() = wallpaperManager.wallpaperInfo == null
fun updateAsync() {
Utilities.THREAD_POOL_EXECUTOR.execute(mUpdateRunnable)
}
private fun updateWallpaper() {
if (applyTask != null) {
updatePending = true
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
Log.d("BWP", "NO permission granted")
return
}
}
val enabled = getEnabledStatus()
if (enabled != isEnabled) {
isEnabled = enabled
runOnMainThread {
listeners.safeForEach(Listener::onEnabledChanged)
}
}
0
if (!isEnabled) {
wallpaper = null
val wm =
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
display.getRealMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
placeholder = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(placeholder!!)
canvas.drawColor(0x44000000)
return
}
var wallpaper = try {
Utilities.drawableToBitmap(wallpaperManager.drawable, true) as Bitmap
} catch (e: Exception) {
runOnMainThread {
val msg = "Failed: ${e.message}"
Toast.makeText(context, msg, Toast.LENGTH_LONG).show()
notifyWallpaperChanged()
}
return
}
wallpaper = scaleAndCropToScreenSize(wallpaper)
wallpaper = applyVibrancy(wallpaper)
applyTask = wallpaperFilter.apply(wallpaper).setCallback {result, error ->
if(error == null) {
this@BlurWallpaperProvider.wallpaper = result
runOnMainThread(::notifyWallpaperChanged)
wallpaper.recycle()
}else {
if (error is OutOfMemoryError) {
runOnMainThread {
Toast.makeText(context, "Failed", Toast.LENGTH_LONG).show()
notifyWallpaperChanged()
}
}
wallpaper.recycle()
}
}
applyTask = null
if (updatePending) {
updatePending = false
updateWallpaper()
}
}
private fun notifyWallpaperChanged() {
listeners.forEach(Listener::onWallpaperChanged)
}
private fun applyVibrancy(wallpaper: Bitmap?): Bitmap {
val width = wallpaper!!.width
val height = wallpaper.height
val bitmap = Bitmap.createBitmap(
width,
height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas()
canvas.setBitmap(bitmap)
val colorMatrix = ColorMatrix()
colorMatrix.setSaturation(1.25f)
val filter = ColorMatrixColorFilter(colorMatrix)
vibrancyPaint.colorFilter = filter
canvas.drawBitmap(wallpaper, 0f, 0f, vibrancyPaint)
wallpaper.recycle()
return bitmap
}
private fun scaleAndCropToScreenSize(wallpaper: Bitmap): Bitmap {
val wm =
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
display.getRealMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val widthFactor = width.toFloat() / wallpaper!!.width
val heightFactor = height.toFloat() / wallpaper.height
val upscaleFactor = Math.max(widthFactor, heightFactor)
if (upscaleFactor <= 0) {
return wallpaper
}
val scaledWidth =
Math.max(width.toFloat(), wallpaper.width * upscaleFactor).toInt()
val scaledHeight =
Math.max(height.toFloat(), wallpaper.height * upscaleFactor).toInt()
var scaledWallpaper =
Bitmap.createScaledBitmap(wallpaper, scaledWidth, scaledHeight, false)
val navigationBarHeight = 0
/*if (BlissLauncher.getApplication(context).getDeviceProfile().hasSoftNavigationBar(context)) {
int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navigationBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
}*/
val y: Int
y = if (scaledWallpaper.height > height) {
(scaledWallpaper.height - height) / 2
} else 0
return Bitmap.createBitmap(
scaledWallpaper,
0,
y,
width,
height - navigationBarHeight
)
}
fun addListener(listener: Listener) {
listeners.add(listener)
}
fun removeListener(listener: Listener) {
listeners.remove(listener)
}
fun createDrawable(): ShaderBlurDrawable {
return ShaderBlurDrawable(this)
}
interface Listener {
fun onWallpaperChanged() {}
fun onEnabledChanged() {}
}
/*fun clear() {
listener = null
cancelPreTask(true)
sInstance = null
}*/
companion object :
SingletonHolder<BlurWallpaperProvider, Context>(ensureOnMainThread(useApplicationContext(::BlurWallpaperProvider))) {
var isEnabled: Boolean = false
private var sEnabledFlag: Int = 0
fun isEnabled(flag: Int): Boolean {
return isEnabled && sEnabledFlag and flag != 0
}
}
}
package foundation.e.blisslauncher.core.blur
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.RectF
import android.graphics.Shader
import android.graphics.drawable.Drawable
import foundation.e.blisslauncher.core.DeviceProfile
class ShaderBlurDrawable internal constructor(private val blurWallpaperProvider: BlurWallpaperProvider) :
Drawable(), BlurWallpaperProvider.Listener {
private var blurAlpha = 255
private val blurPaint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG)
private var blurBitmap: Bitmap? = null
set(value) {
if (field != value) {
field = value
blurPaint.shader =
value?.let { BitmapShader(it, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) }
}
}
private val blurBounds = RectF()
private val blurPath = Path()
private var blurPathValid = false
set(value) {
if (field != value) {
field = value
if (!value) {
invalidateSelf()
}
}
}
var noRadius = true
override fun draw(canvas: Canvas) = draw(canvas, noRadius)
fun draw(canvas: Canvas, noRadius: Boolean = false) {
if (blurAlpha == 0) return
blurBitmap = blurWallpaperProvider.wallpaper
if(blurBitmap == null) {
blurBitmap = blurWallpaperProvider.placeholder
}
blurBitmap =
if (blurBitmap!!.height > (blurBounds.bottom.toInt() - blurBounds.top.toInt())) {
Bitmap.createBitmap(
blurBitmap!!,