Commit a3cf67b6 authored by Romain Hunault's avatar Romain Hunault
Browse files

Merge branch 'sprint_cordoba' into 'master'

[RELEASE] Sprint cordoba

Closes #138 and #159

See merge request e/apps/BlissLauncher!18
parents d1dee091 adeae52f
Pipeline #34421 passed with stage
in 6 minutes and 50 seconds
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
// Manifest version information!
def versionMajor = 1
def versionMinor = 2
def versionPatch = 4
def versionMinor = 3
def versionPatch = 0
android {
compileSdkVersion rootProject.ext.compileSdkVersion
......@@ -16,7 +16,7 @@ android {
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
renderscriptTargetApi 18
renderscriptTargetApi 28
renderscriptSupportModeEnabled true
}
buildTypes {
......@@ -83,8 +83,6 @@ dependencies {
apiNougatImplementation 'org.cyanogenmod:platform.sdk:6.0'
apiOreoImplementation files('libs/lineage-sdk.jar')
debugImplementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
debugImplementation 'com.google.firebase:firebase-core:16.0.6'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
implementation 'org.greenrobot:eventbus:3.1.1'
......@@ -111,6 +109,11 @@ dependencies {
// Rx Relay
implementation "com.jakewharton.rxrelay2:rxrelay:${rootProject.ext.rxRelayVersion}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Blur Library
implementation project(':hoko-blur')
// Room
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
......@@ -140,5 +143,4 @@ dependencies {
androidTestImplementation "com.android.support.test:rules:${rootProject.ext.runnerRulesVersion}"
}
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.getkeepsafe.dexcount'
......@@ -31,12 +31,15 @@
<uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
<uses-permission android:name="lineageos.permission.READ_WEATHER" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS"/>
<application
android:name=".BlissLauncher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity
android:name=".features.launcher.LauncherActivity"
......
......@@ -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) {
......
......@@ -10,7 +10,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
......@@ -28,6 +27,7 @@ public class DeviceProfile {
public static Path path;
private final float widthCm;
private int statusBarHeight;
public int cellHeightWithoutPaddingPx;
public int hotseatCellHeightWithoutPaddingPx;
public int fillResIconDpi;
......@@ -158,13 +158,18 @@ public class DeviceProfile {
widthPx = realSize.x;
double x = widthPx / dm.xdpi;
widthCm = (float) (x * 2.540001f);
Log.i(TAG, "DeviceProfile: " + availableWidthPx);
Log.i(TAG, "DeviceProfile: " + widthPx);
heightPx = realSize.y;
context = getContext(context, Configuration.ORIENTATION_PORTRAIT);
Resources res = context.getResources();
// status bar height
statusBarHeight = 0;
int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = res.getDimensionPixelSize(resourceId);
}
ComponentName cn = new ComponentName(context.getPackageName(),
this.getClass().getName());
......@@ -248,8 +253,6 @@ public class DeviceProfile {
dateTextBottomPadding = (dateTextviewHeight - (int) (0.86 * Utilities.calculateTextHeight(
(float) dateTextSize / 2))) / 2;
Log.i(TAG, "datepadding: " + dateTextTopPadding + "*" + dateTextBottomPadding);
cellHeightWithoutPaddingPx = iconSizePx + Utilities.pxFromDp(4, dm)
+ Utilities.calculateTextHeight(iconTextSizePx);
......@@ -304,7 +307,7 @@ public class DeviceProfile {
return pageIndicatorSizePx + pageIndicatorBottomPaddingPx + pageIndicatorTopPaddingPx;
}
public int getMaxWidgetWidth(){
public int getMaxWidgetWidth() {
return maxWidgetWidth;
}
......@@ -351,6 +354,34 @@ public class DeviceProfile {
return resizedPath;
}
public boolean hasSoftNavigationBar(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
Point smallestSize = new Point();
Point largestSize = new Point();
display.getCurrentSizeRange(smallestSize, largestSize);
int availableHeight = largestSize.y;
Point realSize = new Point();
display.getRealSize(realSize);
int realHeight = realSize.y;
context = getContext(context, Configuration.ORIENTATION_PORTRAIT);
Resources res = context.getResources();
// status bar height
statusBarHeight = 0;
int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = res.getDimensionPixelSize(resourceId);
}
return (realHeight - availableHeight - statusBarHeight) > 0;
}
private int getLauncherIconDensity(int requiredSize) {
// Densities typically defined by an app.
int[] densityBuckets = new int[]{
......
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
......@@ -2,7 +2,10 @@ package foundation.e.blisslauncher.core;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.DisplayMetrics;
......@@ -16,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;
......@@ -41,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.
*/
......@@ -159,6 +179,38 @@ public class Utilities {
return result;
}
public static Bitmap drawableToBitmap(Drawable drawable) {
return Utilities.drawableToBitmap(drawable, true);
}
public static Bitmap drawableToBitmap(Drawable drawable, boolean forceCreate) {
return drawableToBitmap(drawable, forceCreate, 0);
}
public static Bitmap drawableToBitmap(Drawable drawable, boolean forceCreate, int fallbackSize) {
if (!forceCreate && drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (width <= 0 || height <= 0) {
if (fallbackSize > 0) {
width = height = fallbackSize;
} else {
return null;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static boolean isBootCompleted() {
return "1".equals(getSystemProperty("sys.boot_completed", "1"));
}
......@@ -177,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_OPENGL)
.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.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
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.*
import kotlin.math.max
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 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
}
// Prepare a placeholder before hand so that it can be used in case wallpaper is 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)
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)
}
}
if (!isEnabled) {
wallpaper = null
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 =
max(width.toFloat(), wallpaper.width * upscaleFactor).toInt()
val scaledHeight =
max(height.toFloat(), wallpaper.height * upscaleFactor).toInt()
val 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)