Add Spotless

This commit is contained in:
Hemanth S 2020-10-06 14:16:04 +05:30
parent 2af13a4e6c
commit defcd86152
286 changed files with 15604 additions and 13757 deletions

View file

@ -17,42 +17,38 @@ package code.name.monkey.retromusic.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import com.bumptech.glide.signature.StringSignature;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
/** @author Karim Abou Zeid (kabouzeid) */
public class ArtistSignatureUtil {
private static final String ARTIST_SIGNATURE_PREFS = "artist_signatures";
private static final String ARTIST_SIGNATURE_PREFS = "artist_signatures";
private static ArtistSignatureUtil sInstance;
private static ArtistSignatureUtil sInstance;
private final SharedPreferences mPreferences;
private final SharedPreferences mPreferences;
private ArtistSignatureUtil(@NonNull final Context context) {
mPreferences = context.getSharedPreferences(ARTIST_SIGNATURE_PREFS, Context.MODE_PRIVATE);
private ArtistSignatureUtil(@NonNull final Context context) {
mPreferences = context.getSharedPreferences(ARTIST_SIGNATURE_PREFS, Context.MODE_PRIVATE);
}
public static ArtistSignatureUtil getInstance(@NonNull final Context context) {
if (sInstance == null) {
sInstance = new ArtistSignatureUtil(context.getApplicationContext());
}
return sInstance;
}
public static ArtistSignatureUtil getInstance(@NonNull final Context context) {
if (sInstance == null) {
sInstance = new ArtistSignatureUtil(context.getApplicationContext());
}
return sInstance;
}
@SuppressLint("CommitPrefEdits")
public void updateArtistSignature(String artistName) {
mPreferences.edit().putLong(artistName, System.currentTimeMillis()).commit();
}
@SuppressLint("CommitPrefEdits")
public void updateArtistSignature(String artistName) {
mPreferences.edit().putLong(artistName, System.currentTimeMillis()).commit();
}
public long getArtistSignatureRaw(String artistName) {
return mPreferences.getLong(artistName, 0);
}
public long getArtistSignatureRaw(String artistName) {
return mPreferences.getLong(artistName, 0);
}
public StringSignature getArtistSignature(String artistName) {
return new StringSignature(String.valueOf(getArtistSignatureRaw(artistName)));
}
public StringSignature getArtistSignature(String artistName) {
return new StringSignature(String.valueOf(getArtistSignatureRaw(artistName)));
}
}

View file

@ -8,182 +8,181 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import androidx.annotation.NonNull;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.Song;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.Song;
public class AutoGeneratedPlaylistBitmap {
private static final String TAG = "AutoGeneratedPB";
private static final String TAG = "AutoGeneratedPB";
/*
public static Bitmap getBitmapWithCollectionFrame(Context context, List<Song> songPlaylist, boolean round, boolean blur) {
Bitmap bitmap = getBitmap(context,songPlaylist,round,blur);
int w = bitmap.getWidth();
Bitmap ret = Bitmap.createBitmap(w,w,Bitmap.Config.ARGB_8888);
}
*/
public static Bitmap getBitmap(
Context context, List<Song> songPlaylist, boolean round, boolean blur) {
if (songPlaylist == null) return null;
long start = System.currentTimeMillis();
// lấy toàn bộ album id, loại bỏ trùng nhau
List<Long> albumID = new ArrayList<>();
for (Song song : songPlaylist) {
if (!albumID.contains(song.getAlbumId())) albumID.add(song.getAlbumId());
}
long start2 = System.currentTimeMillis() - start;
// lấy toàn bộ art tồn tại
List<Bitmap> art = new ArrayList<Bitmap>();
for (Long id : albumID) {
Bitmap bitmap = getBitmapWithAlbumId(context, id);
if (bitmap != null) art.add(bitmap);
if (art.size() == 6) break;
}
return MergedImageUtils.INSTANCE.joinImages(art);
/*
public static Bitmap getBitmapWithCollectionFrame(Context context, List<Song> songPlaylist, boolean round, boolean blur) {
Bitmap bitmap = getBitmap(context,songPlaylist,round,blur);
int w = bitmap.getWidth();
Bitmap ret = Bitmap.createBitmap(w,w,Bitmap.Config.ARGB_8888);
long start3 = System.currentTimeMillis() - start2 - start;
Bitmap ret;
switch (art.size()) {
// lấy hình mặc định
case 0:
ret = getDefaultBitmap(context, round).copy(Bitmap.Config.ARGB_8888, false);
break;
// dùng hình duy nhất
case 1:
if (round)
ret = BitmapEditor.getRoundedCornerBitmap(art.get(0), art.get(0).getWidth() / 40);
else ret = art.get(0);
break;
// từ 2 trở lên ta cần vẽ canvas
default:
ret = getBitmapCollection(art, round);
}
*/
public static Bitmap getBitmap(Context context, List<Song> songPlaylist, boolean round, boolean blur) {
if (songPlaylist == null) return null;
long start = System.currentTimeMillis();
// lấy toàn bộ album id, loại bỏ trùng nhau
List<Long> albumID = new ArrayList<>();
for (Song song : songPlaylist) {
if (!albumID.contains(song.getAlbumId())) albumID.add(song.getAlbumId());
}
int w = ret.getWidth();
if (blur)
return BitmapEditor.GetRoundedBitmapWithBlurShadow(context, ret, w / 24, w / 24, w / 24, w / 24, 0, 200, w / 40, 1);
long start2 = System.currentTimeMillis() - start;
Log.d(TAG, "getBitmap: time = " + (System.currentTimeMillis() - start) + ", start2 = " + start2 + ", start3 = " + start3);
return ret;*/
}
// lấy toàn bộ art tồn tại
List<Bitmap> art = new ArrayList<Bitmap>();
for (Long id : albumID) {
Bitmap bitmap = getBitmapWithAlbumId(context, id);
if (bitmap != null) art.add(bitmap);
if (art.size() == 6) break;
}
return MergedImageUtils.INSTANCE.joinImages(art);
/*
private static Bitmap getBitmapCollection(ArrayList<Bitmap> art, boolean round) {
long start = System.currentTimeMillis();
// lấy kích thước kích thước của bitmap lớn nhất
int max_width = art.get(0).getWidth();
for (Bitmap b : art) if (max_width < b.getWidth()) max_width = b.getWidth();
Bitmap bitmap = Bitmap.createBitmap(max_width, max_width, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(false);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(max_width / 100);
paint.setColor(0xffffffff);
switch (art.size()) {
case 2:
canvas.drawBitmap(art.get(1), null, new Rect(0, 0, max_width, max_width), null);
canvas.drawBitmap(
art.get(0), null, new Rect(-max_width / 2, 0, max_width / 2, max_width), null);
canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint);
break;
case 3:
canvas.drawBitmap(
art.get(0), null, new Rect(-max_width / 4, 0, 3 * max_width / 4, max_width), null);
canvas.drawBitmap(
art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null);
canvas.drawBitmap(
art.get(2), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null);
canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint);
canvas.drawLine(max_width / 2, max_width / 2, max_width, max_width / 2, paint);
break;
case 4:
canvas.drawBitmap(art.get(0), null, new Rect(0, 0, max_width / 2, max_width / 2), null);
canvas.drawBitmap(
art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null);
canvas.drawBitmap(
art.get(2), null, new Rect(0, max_width / 2, max_width / 2, max_width), null);
canvas.drawBitmap(
art.get(3), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null);
canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint);
canvas.drawLine(0, max_width / 2, max_width, max_width / 2, paint);
break;
// default: canvas.drawBitmap(art.get(0),null,new Rect(0,0,max_width,max_width),null);
default:
long start3 = System.currentTimeMillis() - start2 - start;
Bitmap ret;
switch (art.size()) {
// lấy hình mặc định
// độ rộng của des bitmap
float w = (float) (Math.sqrt(2) / 2 * max_width);
float b = (float) (max_width / Math.sqrt(5));
// khoảng cách định nghĩa, dùng để tính vị trí tâm của 4 bức hình xung quanh
float d = (float) (max_width * (0.5f - 1 / Math.sqrt(10)));
float deg = 45;
for (int i = 0; i < 5; i++) {
canvas.save();
switch (i) {
case 0:
ret = getDefaultBitmap(context, round).copy(Bitmap.Config.ARGB_8888, false);
break;
// dùng hình duy nhất
canvas.translate(max_width / 2, max_width / 2);
canvas.rotate(deg);
// b = (float) (max_width*Math.sqrt(2/5f));
canvas.drawBitmap(art.get(0), null, new RectF(-b / 2, -b / 2, b / 2, b / 2), null);
break;
case 1:
if (round)
ret = BitmapEditor.getRoundedCornerBitmap(art.get(0), art.get(0).getWidth() / 40);
else ret = art.get(0);
break;
// từ 2 trở lên ta cần vẽ canvas
default:
ret = getBitmapCollection(art, round);
}
int w = ret.getWidth();
if (blur)
return BitmapEditor.GetRoundedBitmapWithBlurShadow(context, ret, w / 24, w / 24, w / 24, w / 24, 0, 200, w / 40, 1);
Log.d(TAG, "getBitmap: time = " + (System.currentTimeMillis() - start) + ", start2 = " + start2 + ", start3 = " + start3);
return ret;*/
}
private static Bitmap getBitmapCollection(ArrayList<Bitmap> art, boolean round) {
long start = System.currentTimeMillis();
// lấy kích thước kích thước của bitmap lớn nhất
int max_width = art.get(0).getWidth();
for (Bitmap b : art) if (max_width < b.getWidth()) max_width = b.getWidth();
Bitmap bitmap = Bitmap.createBitmap(max_width, max_width, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(false);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(max_width / 100);
paint.setColor(0xffffffff);
switch (art.size()) {
canvas.translate(d, 0);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(w / 2, -w / 2, w / 2, w / 2, paint);
break;
case 2:
canvas.drawBitmap(art.get(1), null, new Rect(0, 0, max_width, max_width), null);
canvas.drawBitmap(art.get(0), null, new Rect(-max_width / 2, 0, max_width / 2, max_width), null);
canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint);
break;
canvas.translate(max_width, d);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(-w / 2, w / 2, w / 2, w / 2, paint);
break;
case 3:
canvas.drawBitmap(art.get(0), null, new Rect(-max_width / 4, 0, 3 * max_width / 4, max_width), null);
canvas.drawBitmap(art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null);
canvas.drawBitmap(art.get(2), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null);
canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint);
canvas.drawLine(max_width / 2, max_width / 2, max_width, max_width / 2, paint);
break;
canvas.translate(max_width - d, max_width);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(-w / 2, -w / 2, -w / 2, w / 2, paint);
break;
case 4:
canvas.drawBitmap(art.get(0), null, new Rect(0, 0, max_width / 2, max_width / 2), null);
canvas.drawBitmap(art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null);
canvas.drawBitmap(art.get(2), null, new Rect(0, max_width / 2, max_width / 2, max_width), null);
canvas.drawBitmap(art.get(3), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null);
canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint);
canvas.drawLine(0, max_width / 2, max_width, max_width / 2, paint);
break;
// default: canvas.drawBitmap(art.get(0),null,new Rect(0,0,max_width,max_width),null);
default:
// độ rộng của des bitmap
float w = (float) (Math.sqrt(2) / 2 * max_width);
float b = (float) (max_width / Math.sqrt(5));
// khoảng cách định nghĩa, dùng để tính vị trí tâm của 4 bức hình xung quanh
float d = (float) (max_width * (0.5f - 1 / Math.sqrt(10)));
float deg = 45;
for (int i = 0; i < 5; i++) {
canvas.save();
switch (i) {
case 0:
canvas.translate(max_width / 2, max_width / 2);
canvas.rotate(deg);
// b = (float) (max_width*Math.sqrt(2/5f));
canvas.drawBitmap(art.get(0), null, new RectF(-b / 2, -b / 2, b / 2, b / 2), null);
break;
case 1:
canvas.translate(d, 0);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(w / 2, -w / 2, w / 2, w / 2, paint);
break;
case 2:
canvas.translate(max_width, d);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(-w / 2, w / 2, w / 2, w / 2, paint);
break;
case 3:
canvas.translate(max_width - d, max_width);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(-w / 2, -w / 2, -w / 2, w / 2, paint);
break;
case 4:
canvas.translate(0, max_width - d);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(-w / 2, -w / 2, w / 2, -w / 2, paint);
break;
}
canvas.restore();
}
}
Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start));
if (round)
return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40);
else return bitmap;
}
private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Long id) {
try {
return Glide.with(context)
.load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id))
.asBitmap()
.into(200, 200)
.get();
} catch (Exception e) {
return null;
canvas.translate(0, max_width - d);
canvas.rotate(deg);
canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null);
paint.setAntiAlias(true);
canvas.drawLine(-w / 2, -w / 2, w / 2, -w / 2, paint);
break;
}
canvas.restore();
}
}
Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start));
if (round) return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40);
else return bitmap;
}
public static Bitmap getDefaultBitmap(@NonNull Context context, boolean round) {
if (round)
return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art);
return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art);
private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Long id) {
try {
return Glide.with(context)
.load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id))
.asBitmap()
.into(200, 200)
.get();
} catch (Exception e) {
return null;
}
}
}
public static Bitmap getDefaultBitmap(@NonNull Context context, boolean round) {
if (round)
return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art);
return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art);
}
}

View file

@ -17,126 +17,124 @@ package code.name.monkey.retromusic.util;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* @author Eugene Cheung (arkon)
*/
/** @author Eugene Cheung (arkon) */
public class CalendarUtil {
private static final long MS_PER_MINUTE = 60 * 1000;
private static final long MS_PER_DAY = 24 * 60 * MS_PER_MINUTE;
private static final long MS_PER_MINUTE = 60 * 1000;
private static final long MS_PER_DAY = 24 * 60 * MS_PER_MINUTE;
private Calendar calendar;
private Calendar calendar;
public CalendarUtil() {
this.calendar = Calendar.getInstance();
public CalendarUtil() {
this.calendar = Calendar.getInstance();
}
/**
* Returns the time elapsed so far today in milliseconds.
*
* @return Time elapsed today in milliseconds.
*/
public long getElapsedToday() {
// Time elapsed so far today
return (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * MS_PER_MINUTE
+ calendar.get(Calendar.SECOND) * 1000
+ calendar.get(Calendar.MILLISECOND);
}
/**
* Returns the time elapsed so far this week in milliseconds.
*
* @return Time elapsed this week in milliseconds.
*/
public long getElapsedWeek() {
// Today + days passed this week
long elapsed = getElapsedToday();
final int passedWeekdays =
calendar.get(Calendar.DAY_OF_WEEK) - 1 - calendar.getFirstDayOfWeek();
if (passedWeekdays > 0) {
elapsed += passedWeekdays * MS_PER_DAY;
}
/**
* Returns the time elapsed so far today in milliseconds.
*
* @return Time elapsed today in milliseconds.
*/
public long getElapsedToday() {
// Time elapsed so far today
return (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * MS_PER_MINUTE
+ calendar.get(Calendar.SECOND) * 1000
+ calendar.get(Calendar.MILLISECOND);
return elapsed;
}
/**
* Returns the time elapsed so far this month in milliseconds.
*
* @return Time elapsed this month in milliseconds.
*/
public long getElapsedMonth() {
// Today + rest of this month
return getElapsedToday() + ((calendar.get(Calendar.DAY_OF_MONTH) - 1) * MS_PER_DAY);
}
/**
* Returns the time elapsed so far this month and the last numMonths months in milliseconds.
*
* @param numMonths Additional number of months prior to the current month to calculate.
* @return Time elapsed this month and the last numMonths months in milliseconds.
*/
public long getElapsedMonths(int numMonths) {
// Today + rest of this month
long elapsed = getElapsedMonth();
// Previous numMonths months
int month = calendar.get(Calendar.MONTH);
int year = calendar.get(Calendar.YEAR);
for (int i = 0; i < numMonths; i++) {
month--;
if (month < Calendar.JANUARY) {
month = Calendar.DECEMBER;
year--;
}
elapsed += getDaysInMonth(month) * MS_PER_DAY;
}
/**
* Returns the time elapsed so far this week in milliseconds.
*
* @return Time elapsed this week in milliseconds.
*/
public long getElapsedWeek() {
// Today + days passed this week
long elapsed = getElapsedToday();
return elapsed;
}
final int passedWeekdays = calendar.get(Calendar.DAY_OF_WEEK) - 1 - calendar.getFirstDayOfWeek();
if (passedWeekdays > 0) {
elapsed += passedWeekdays * MS_PER_DAY;
}
/**
* Returns the time elapsed so far this year in milliseconds.
*
* @return Time elapsed this year in milliseconds.
*/
public long getElapsedYear() {
// Today + rest of this month + previous months until January
long elapsed = getElapsedMonth();
return elapsed;
int month = calendar.get(Calendar.MONTH) - 1;
int year = calendar.get(Calendar.YEAR);
while (month > Calendar.JANUARY) {
elapsed += getDaysInMonth(month) * MS_PER_DAY;
month--;
}
/**
* Returns the time elapsed so far this month in milliseconds.
*
* @return Time elapsed this month in milliseconds.
*/
public long getElapsedMonth() {
// Today + rest of this month
return getElapsedToday() +
((calendar.get(Calendar.DAY_OF_MONTH) - 1) * MS_PER_DAY);
}
return elapsed;
}
/**
* Returns the time elapsed so far this month and the last numMonths months in milliseconds.
*
* @param numMonths Additional number of months prior to the current month to calculate.
* @return Time elapsed this month and the last numMonths months in milliseconds.
*/
public long getElapsedMonths(int numMonths) {
// Today + rest of this month
long elapsed = getElapsedMonth();
/**
* Gets the number of days for the given month in the given year.
*
* @param month The month (1 - 12).
* @return The days in that month/year.
*/
private int getDaysInMonth(int month) {
final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1);
return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH);
}
// Previous numMonths months
int month = calendar.get(Calendar.MONTH);
int year = calendar.get(Calendar.YEAR);
for (int i = 0; i < numMonths; i++) {
month--;
/**
* Returns the time elapsed so far last N days in milliseconds.
*
* @return Time elapsed since N days in milliseconds.
*/
public long getElapsedDays(int numDays) {
long elapsed = getElapsedToday();
elapsed += numDays * MS_PER_DAY;
if (month < Calendar.JANUARY) {
month = Calendar.DECEMBER;
year--;
}
elapsed += getDaysInMonth(month) * MS_PER_DAY;
}
return elapsed;
}
/**
* Returns the time elapsed so far this year in milliseconds.
*
* @return Time elapsed this year in milliseconds.
*/
public long getElapsedYear() {
// Today + rest of this month + previous months until January
long elapsed = getElapsedMonth();
int month = calendar.get(Calendar.MONTH) - 1;
int year = calendar.get(Calendar.YEAR);
while (month > Calendar.JANUARY) {
elapsed += getDaysInMonth(month) * MS_PER_DAY;
month--;
}
return elapsed;
}
/**
* Gets the number of days for the given month in the given year.
*
* @param month The month (1 - 12).
* @return The days in that month/year.
*/
private int getDaysInMonth(int month) {
final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1);
return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH);
}
/**
* Returns the time elapsed so far last N days in milliseconds.
*
* @return Time elapsed since N days in milliseconds.
*/
public long getElapsedDays(int numDays) {
long elapsed = getElapsedToday();
elapsed += numDays * MS_PER_DAY;
return elapsed;
}
return elapsed;
}
}

View file

@ -1,58 +1,55 @@
package code.name.monkey.retromusic.util;
import android.graphics.Bitmap;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.palette.graphics.Palette;
import java.util.Collections;
import java.util.Comparator;
public class ColorUtil {
@Nullable
public static Palette generatePalette(Bitmap bitmap) {
if (bitmap == null) return null;
return Palette.from(bitmap).generate();
@Nullable
public static Palette generatePalette(Bitmap bitmap) {
if (bitmap == null) return null;
return Palette.from(bitmap).generate();
}
@ColorInt
public static int getColor(@Nullable Palette palette, int fallback) {
if (palette != null) {
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch().getRgb();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch().getRgb();
} else if (palette.getDarkVibrantSwatch() != null) {
return palette.getDarkVibrantSwatch().getRgb();
} else if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch().getRgb();
} else if (palette.getLightVibrantSwatch() != null) {
return palette.getLightVibrantSwatch().getRgb();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch().getRgb();
} else if (!palette.getSwatches().isEmpty()) {
return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb();
}
}
return fallback;
}
private static class SwatchComparator implements Comparator<Palette.Swatch> {
private static SwatchComparator sInstance;
static SwatchComparator getInstance() {
if (sInstance == null) {
sInstance = new SwatchComparator();
}
return sInstance;
}
@ColorInt
public static int getColor(@Nullable Palette palette, int fallback) {
if (palette != null) {
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch().getRgb();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch().getRgb();
} else if (palette.getDarkVibrantSwatch() != null) {
return palette.getDarkVibrantSwatch().getRgb();
} else if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch().getRgb();
} else if (palette.getLightVibrantSwatch() != null) {
return palette.getLightVibrantSwatch().getRgb();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch().getRgb();
} else if (!palette.getSwatches().isEmpty()) {
return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb();
}
}
return fallback;
@Override
public int compare(Palette.Swatch lhs, Palette.Swatch rhs) {
return lhs.getPopulation() - rhs.getPopulation();
}
private static class SwatchComparator implements Comparator<Palette.Swatch> {
private static SwatchComparator sInstance;
static SwatchComparator getInstance() {
if (sInstance == null) {
sInstance = new SwatchComparator();
}
return sInstance;
}
@Override
public int compare(Palette.Swatch lhs, Palette.Swatch rhs) {
return lhs.getPopulation() - rhs.getPopulation();
}
}
}
}
}

View file

@ -16,63 +16,64 @@ package code.name.monkey.retromusic.util;
import android.content.Context;
import android.graphics.Bitmap;
import java.io.File;
import java.io.IOException;
/**
* Created on : June 18, 2016
* Author : zetbaitsu
* Name : Zetra
* GitHub : https://github.com/zetbaitsu
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : https://github.com/zetbaitsu
*/
public class Compressor {
//max width and height values of the compressed image is taken as 612x816
private int maxWidth = 612;
private int maxHeight = 816;
private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
private int quality = 80;
private String destinationDirectoryPath;
// max width and height values of the compressed image is taken as 612x816
private int maxWidth = 612;
private int maxHeight = 816;
private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
private int quality = 80;
private String destinationDirectoryPath;
public Compressor(Context context) {
destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images";
}
public Compressor(Context context) {
destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images";
}
public Compressor setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
return this;
}
public Compressor setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
return this;
}
public Compressor setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
return this;
}
public Compressor setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
return this;
}
public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) {
this.compressFormat = compressFormat;
return this;
}
public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) {
this.compressFormat = compressFormat;
return this;
}
public Compressor setQuality(int quality) {
this.quality = quality;
return this;
}
public Compressor setQuality(int quality) {
this.quality = quality;
return this;
}
public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) {
this.destinationDirectoryPath = destinationDirectoryPath;
return this;
}
public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) {
this.destinationDirectoryPath = destinationDirectoryPath;
return this;
}
public File compressToFile(File imageFile) throws IOException {
return compressToFile(imageFile, imageFile.getName());
}
public File compressToFile(File imageFile) throws IOException {
return compressToFile(imageFile, imageFile.getName());
}
public File compressToFile(File imageFile, String compressedFileName) throws IOException {
return ImageUtil.compressImage(imageFile, maxWidth, maxHeight, compressFormat, quality,
destinationDirectoryPath + File.separator + compressedFileName);
}
public File compressToFile(File imageFile, String compressedFileName) throws IOException {
return ImageUtil.compressImage(
imageFile,
maxWidth,
maxHeight,
compressFormat,
quality,
destinationDirectoryPath + File.separator + compressedFileName);
}
public Bitmap compressToBitmap(File imageFile) throws IOException {
return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight);
}
public Bitmap compressToBitmap(File imageFile) throws IOException {
return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight);
}
}

View file

@ -19,10 +19,11 @@ import android.database.Cursor;
import android.os.Environment;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
import code.name.monkey.retromusic.repository.SortedCursor;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -36,228 +37,222 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
import code.name.monkey.retromusic.repository.SortedCursor;
public final class FileUtil {
private FileUtil() {
private FileUtil() {}
public static byte[] readBytes(InputStream stream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int count;
while ((count = stream.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
stream.close();
return baos.toByteArray();
}
@NonNull
public static List<Song> matchFilesWithMediaStore(
@NonNull Context context, @Nullable List<File> files) {
return new RealSongRepository(context).songs(makeSongCursor(context, files));
}
public static String safeGetCanonicalPath(File file) {
try {
return file.getCanonicalPath();
} catch (IOException e) {
e.printStackTrace();
return file.getAbsolutePath();
}
}
@Nullable
public static SortedCursor makeSongCursor(
@NonNull final Context context, @Nullable final List<File> files) {
String selection = null;
String[] paths = null;
if (files != null) {
paths = toPathArray(files);
if (files.size() > 0
&& files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle.
selection =
MediaStore.Audio.AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")";
}
}
public static byte[] readBytes(InputStream stream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int count;
while ((count = stream.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
stream.close();
return baos.toByteArray();
}
Cursor songCursor =
new RealSongRepository(context).makeSongCursor(selection, selection == null ? null : paths);
@NonNull
public static List<Song> matchFilesWithMediaStore(@NonNull Context context,
@Nullable List<File> files) {
return new RealSongRepository(context).songs(makeSongCursor(context, files));
}
return songCursor == null
? null
: new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA);
}
public static String safeGetCanonicalPath(File file) {
try {
return file.getCanonicalPath();
private static String makePlaceholders(int len) {
StringBuilder sb = new StringBuilder(len * 2 - 1);
sb.append("?");
for (int i = 1; i < len; i++) {
sb.append(",?");
}
return sb.toString();
}
@Nullable
private static String[] toPathArray(@Nullable List<File> files) {
if (files != null) {
String[] paths = new String[files.size()];
for (int i = 0; i < files.size(); i++) {
/*try {
paths[i] = files.get(i).getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later
} catch (IOException e) {
e.printStackTrace();
return file.getAbsolutePath();
}
paths[i] = files.get(i).getPath();
}*/
paths[i] = safeGetCanonicalPath(files.get(i));
}
return paths;
}
return null;
}
@Nullable
public static SortedCursor makeSongCursor(@NonNull final Context context,
@Nullable final List<File> files) {
String selection = null;
String[] paths = null;
if (files != null) {
paths = toPathArray(files);
if (files.size() > 0
&& files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle.
selection =
MediaStore.Audio.AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")";
}
}
Cursor songCursor = new RealSongRepository(context).makeSongCursor(selection, selection == null ? null : paths);
return songCursor == null ? null
: new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA);
@NonNull
public static List<File> listFiles(@NonNull File directory, @Nullable FileFilter fileFilter) {
List<File> fileList = new LinkedList<>();
File[] found = directory.listFiles(fileFilter);
if (found != null) {
Collections.addAll(fileList, found);
}
return fileList;
}
private static String makePlaceholders(int len) {
StringBuilder sb = new StringBuilder(len * 2 - 1);
sb.append("?");
for (int i = 1; i < len; i++) {
sb.append(",?");
}
return sb.toString();
@NonNull
public static List<File> listFilesDeep(@NonNull File directory, @Nullable FileFilter fileFilter) {
List<File> files = new LinkedList<>();
internalListFilesDeep(files, directory, fileFilter);
return files;
}
@NonNull
public static List<File> listFilesDeep(
@NonNull Collection<File> files, @Nullable FileFilter fileFilter) {
List<File> resFiles = new LinkedList<>();
for (File file : files) {
if (file.isDirectory()) {
internalListFilesDeep(resFiles, file, fileFilter);
} else if (fileFilter == null || fileFilter.accept(file)) {
resFiles.add(file);
}
}
return resFiles;
}
@Nullable
private static String[] toPathArray(@Nullable List<File> files) {
if (files != null) {
String[] paths = new String[files.size()];
for (int i = 0; i < files.size(); i++) {
/*try {
paths[i] = files.get(i).getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later
} catch (IOException e) {
e.printStackTrace();
paths[i] = files.get(i).getPath();
}*/
paths[i] = safeGetCanonicalPath(files.get(i));
}
return paths;
}
return null;
}
private static void internalListFilesDeep(
@NonNull Collection<File> files, @NonNull File directory, @Nullable FileFilter fileFilter) {
File[] found = directory.listFiles(fileFilter);
@NonNull
public static List<File> listFiles(@NonNull File directory, @Nullable FileFilter fileFilter) {
List<File> fileList = new LinkedList<>();
File[] found = directory.listFiles(fileFilter);
if (found != null) {
Collections.addAll(fileList, found);
}
return fileList;
}
@NonNull
public static List<File> listFilesDeep(@NonNull File directory, @Nullable FileFilter fileFilter) {
List<File> files = new LinkedList<>();
internalListFilesDeep(files, directory, fileFilter);
return files;
}
@NonNull
public static List<File> listFilesDeep(@NonNull Collection<File> files,
@Nullable FileFilter fileFilter) {
List<File> resFiles = new LinkedList<>();
for (File file : files) {
if (file.isDirectory()) {
internalListFilesDeep(resFiles, file, fileFilter);
} else if (fileFilter == null || fileFilter.accept(file)) {
resFiles.add(file);
}
}
return resFiles;
}
private static void internalListFilesDeep(@NonNull Collection<File> files,
@NonNull File directory, @Nullable FileFilter fileFilter) {
File[] found = directory.listFiles(fileFilter);
if (found != null) {
for (File file : found) {
if (file.isDirectory()) {
internalListFilesDeep(files, file, fileFilter);
} else {
files.add(file);
}
}
}
}
public static boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) {
if (mimeType == null || mimeType.equals("*/*")) {
return true;
if (found != null) {
for (File file : found) {
if (file.isDirectory()) {
internalListFilesDeep(files, file, fileFilter);
} else {
// get the file mime type
String filename = file.toURI().toString();
int dotPos = filename.lastIndexOf('.');
if (dotPos == -1) {
return false;
}
String fileExtension = filename.substring(dotPos + 1).toLowerCase();
String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension);
if (fileType == null) {
return false;
}
// check the 'type/subtype' pattern
if (fileType.equals(mimeType)) {
return true;
}
// check the 'type/*' pattern
int mimeTypeDelimiter = mimeType.lastIndexOf('/');
if (mimeTypeDelimiter == -1) {
return false;
}
String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter);
String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1);
if (!mimeTypeSubtype.equals("*")) {
return false;
}
int fileTypeDelimiter = fileType.lastIndexOf('/');
if (fileTypeDelimiter == -1) {
return false;
}
String fileTypeMainType = fileType.substring(0, fileTypeDelimiter);
if (fileTypeMainType.equals(mimeTypeMainType)) {
return true;
}
return fileTypeMainType.equals(mimeTypeMainType);
files.add(file);
}
}
}
}
public static String stripExtension(String str) {
if (str == null) {
return null;
}
int pos = str.lastIndexOf('.');
if (pos == -1) {
return str;
}
return str.substring(0, pos);
public static boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) {
if (mimeType == null || mimeType.equals("*/*")) {
return true;
} else {
// get the file mime type
String filename = file.toURI().toString();
int dotPos = filename.lastIndexOf('.');
if (dotPos == -1) {
return false;
}
String fileExtension = filename.substring(dotPos + 1).toLowerCase();
String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension);
if (fileType == null) {
return false;
}
// check the 'type/subtype' pattern
if (fileType.equals(mimeType)) {
return true;
}
// check the 'type/*' pattern
int mimeTypeDelimiter = mimeType.lastIndexOf('/');
if (mimeTypeDelimiter == -1) {
return false;
}
String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter);
String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1);
if (!mimeTypeSubtype.equals("*")) {
return false;
}
int fileTypeDelimiter = fileType.lastIndexOf('/');
if (fileTypeDelimiter == -1) {
return false;
}
String fileTypeMainType = fileType.substring(0, fileTypeDelimiter);
if (fileTypeMainType.equals(mimeTypeMainType)) {
return true;
}
return fileTypeMainType.equals(mimeTypeMainType);
}
}
public static String readFromStream(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (sb.length() > 0) {
sb.append("\n");
}
sb.append(line);
}
reader.close();
return sb.toString();
public static String stripExtension(String str) {
if (str == null) {
return null;
}
public static String read(File file) throws Exception {
FileInputStream fin = new FileInputStream(file);
String ret = readFromStream(fin);
fin.close();
return ret;
int pos = str.lastIndexOf('.');
if (pos == -1) {
return str;
}
return str.substring(0, pos);
}
public static boolean isExternalMemoryAvailable() {
Boolean isSDPresent = Environment.getExternalStorageState()
.equals(android.os.Environment.MEDIA_MOUNTED);
Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable();
// yes SD-card is present
// Sorry
return isSDSupportedDevice && isSDPresent;
public static String readFromStream(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (sb.length() > 0) {
sb.append("\n");
}
sb.append(line);
}
reader.close();
return sb.toString();
}
public static File safeGetCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
e.printStackTrace();
return file.getAbsoluteFile();
}
public static String read(File file) throws Exception {
FileInputStream fin = new FileInputStream(file);
String ret = readFromStream(fin);
fin.close();
return ret;
}
public static boolean isExternalMemoryAvailable() {
Boolean isSDPresent =
Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable();
// yes SD-card is present
// Sorry
return isSDSupportedDevice && isSDPresent;
}
public static File safeGetCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
e.printStackTrace();
return file.getAbsoluteFile();
}
}
}

View file

@ -26,262 +26,268 @@ import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.os.Build;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import code.name.monkey.appthemehelper.util.TintHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import code.name.monkey.appthemehelper.util.TintHelper;
/**
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub :
* https://github.com/zetbaitsu
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : https://github.com/zetbaitsu
*/
public class ImageUtil {
private static final int TOLERANCE = 20;
// Alpha amount for which values below are considered transparent.
private static final int ALPHA_TOLERANCE = 50;
private static int[] mTempBuffer;
private static final int TOLERANCE = 20;
// Alpha amount for which values below are considered transparent.
private static final int ALPHA_TOLERANCE = 50;
private static int[] mTempBuffer;
private ImageUtil() {
private ImageUtil() {}
public static boolean isGrayscale(Bitmap bitmap) {
final int height = bitmap.getHeight();
final int width = bitmap.getWidth();
int size = height * width;
ensureBufferSize(size);
bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height);
for (int i = 0; i < size; i++) {
if (!isGrayscale(mTempBuffer[i])) {
return false;
}
}
return true;
}
public static Bitmap createBitmap(Drawable drawable) {
return createBitmap(drawable, 1f);
}
public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) {
Bitmap bitmap =
Bitmap.createBitmap(
(int) (drawable.getIntrinsicWidth() * sizeMultiplier),
(int) (drawable.getIntrinsicHeight() * sizeMultiplier),
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
drawable.draw(c);
return bitmap;
}
public static Drawable getTintedVectorDrawable(
@NonNull Resources res,
@DrawableRes int resId,
@Nullable Resources.Theme theme,
@ColorInt int color) {
return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color);
}
public static Drawable getTintedVectorDrawable(
@NonNull Context context, @DrawableRes int id, @ColorInt int color) {
return TintHelper.createTintedDrawable(
getVectorDrawable(context.getResources(), id, context.getTheme()), color);
}
public static Drawable getVectorDrawable(@NonNull Context context, @DrawableRes int id) {
return getVectorDrawable(context.getResources(), id, context.getTheme());
}
public static Drawable getVectorDrawable(
@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) {
if (Build.VERSION.SDK_INT >= 21) {
return res.getDrawable(resId, theme);
}
return VectorDrawableCompat.create(res, resId, theme);
}
/** Makes sure that {@code mTempBuffer} has at least length {@code size}. */
private static void ensureBufferSize(int size) {
if (mTempBuffer == null || mTempBuffer.length < size) {
mTempBuffer = new int[size];
}
}
public static Bitmap setBitmapColor(Bitmap bitmap, int color) {
Bitmap result =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1);
Paint paint = new Paint();
paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
Canvas canvas = new Canvas(result);
canvas.drawBitmap(result, 0, 0, paint);
return result;
}
public static boolean isGrayscale(int color) {
int alpha = 0xFF & (color >> 24);
if (alpha < ALPHA_TOLERANCE) {
return true;
}
int r = 0xFF & (color >> 16);
int g = 0xFF & (color >> 8);
int b = 0xFF & color;
return Math.abs(r - g) < TOLERANCE
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
} // Amount (max is 255) that two channels can differ before the color is no longer "gray".
public static Bitmap resizeBitmap(@NonNull Bitmap src, int maxForSmallerSize) {
int width = src.getWidth();
int height = src.getHeight();
final int dstWidth;
final int dstHeight;
if (width < height) {
if (maxForSmallerSize >= width) {
return src;
}
float ratio = (float) height / width;
dstWidth = maxForSmallerSize;
dstHeight = Math.round(maxForSmallerSize * ratio);
} else {
if (maxForSmallerSize >= height) {
return src;
}
float ratio = (float) width / height;
dstWidth = Math.round(maxForSmallerSize * ratio);
dstHeight = maxForSmallerSize;
}
public static boolean isGrayscale(Bitmap bitmap) {
final int height = bitmap.getHeight();
final int width = bitmap.getWidth();
int size = height * width;
ensureBufferSize(size);
bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height);
for (int i = 0; i < size; i++) {
if (!isGrayscale(mTempBuffer[i])) {
return false;
}
}
return true;
return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
}
public static int calculateInSampleSize(int width, int height, int reqWidth) {
// setting reqWidth matching to desired 1:1 ratio and screen-size
if (width < height) {
reqWidth = (height / width) * reqWidth;
} else {
reqWidth = (width / height) * reqWidth;
}
public static Bitmap createBitmap(Drawable drawable) {
return createBitmap(drawable, 1f);
int inSampleSize = 1;
if (height > reqWidth || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqWidth && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) {
Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
drawable.draw(c);
return bitmap;
return inSampleSize;
}
static File compressImage(
File imageFile,
int reqWidth,
int reqHeight,
Bitmap.CompressFormat compressFormat,
int quality,
String destinationPath)
throws IOException {
FileOutputStream fileOutputStream = null;
File file = new File(destinationPath).getParentFile();
if (!file.exists()) {
file.mkdirs();
}
try {
fileOutputStream = new FileOutputStream(destinationPath);
// write the compressed bitmap at the destination specified by destinationPath.
decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight)
.compress(compressFormat, quality, fileOutputStream);
} finally {
if (fileOutputStream != null) {
fileOutputStream.flush();
fileOutputStream.close();
}
}
public static Drawable getTintedVectorDrawable(@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme, @ColorInt int color) {
return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color);
return new File(destinationPath);
}
static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight)
throws IOException {
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
// check the rotation of the image and display it properly
ExifInterface exif;
exif = new ExifInterface(imageFile.getAbsolutePath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
Matrix matrix = new Matrix();
if (orientation == 6) {
matrix.postRotate(90);
} else if (orientation == 3) {
matrix.postRotate(180);
} else if (orientation == 8) {
matrix.postRotate(270);
}
scaledBitmap =
Bitmap.createBitmap(
scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
return scaledBitmap;
}
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id, @ColorInt int color) {
return TintHelper.createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()), color);
return inSampleSize;
}
@NonNull
public static Bitmap getResizedBitmap(@NonNull Bitmap image, int maxSize) {
int width = image.getWidth();
int height = image.getHeight();
float bitmapRatio = (float) width / (float) height;
if (bitmapRatio > 1) {
width = maxSize;
height = (int) (width / bitmapRatio);
} else {
height = maxSize;
width = (int) (height * bitmapRatio);
}
return Bitmap.createScaledBitmap(image, width, height, true);
}
public static Drawable getVectorDrawable(@NonNull Context context, @DrawableRes int id) {
return getVectorDrawable(context.getResources(), id, context.getTheme());
}
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) {
if (Build.VERSION.SDK_INT >= 21) {
return res.getDrawable(resId, theme);
}
return VectorDrawableCompat.create(res, resId, theme);
}
/**
* Makes sure that {@code mTempBuffer} has at least length {@code size}.
*/
private static void ensureBufferSize(int size) {
if (mTempBuffer == null || mTempBuffer.length < size) {
mTempBuffer = new int[size];
}
}
public static Bitmap setBitmapColor(Bitmap bitmap, int color) {
Bitmap result = Bitmap
.createBitmap(bitmap, 0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1);
Paint paint = new Paint();
paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
Canvas canvas = new Canvas(result);
canvas.drawBitmap(result, 0, 0, paint);
return result;
}
public static boolean isGrayscale(int color) {
int alpha = 0xFF & (color >> 24);
if (alpha < ALPHA_TOLERANCE) {
return true;
}
int r = 0xFF & (color >> 16);
int g = 0xFF & (color >> 8);
int b = 0xFF & color;
return Math.abs(r - g) < TOLERANCE
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
} // Amount (max is 255) that two channels can differ before the color is no longer "gray".
public static Bitmap resizeBitmap(@NonNull Bitmap src, int maxForSmallerSize) {
int width = src.getWidth();
int height = src.getHeight();
final int dstWidth;
final int dstHeight;
if (width < height) {
if (maxForSmallerSize >= width) {
return src;
}
float ratio = (float) height / width;
dstWidth = maxForSmallerSize;
dstHeight = Math.round(maxForSmallerSize * ratio);
} else {
if (maxForSmallerSize >= height) {
return src;
}
float ratio = (float) width / height;
dstWidth = Math.round(maxForSmallerSize * ratio);
dstHeight = maxForSmallerSize;
}
return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
}
public static int calculateInSampleSize(int width, int height, int reqWidth) {
// setting reqWidth matching to desired 1:1 ratio and screen-size
if (width < height) {
reqWidth = (height / width) * reqWidth;
} else {
reqWidth = (width / height) * reqWidth;
}
int inSampleSize = 1;
if (height > reqWidth || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqWidth
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
static File compressImage(File imageFile, int reqWidth, int reqHeight,
Bitmap.CompressFormat compressFormat, int quality, String destinationPath)
throws IOException {
FileOutputStream fileOutputStream = null;
File file = new File(destinationPath).getParentFile();
if (!file.exists()) {
file.mkdirs();
}
try {
fileOutputStream = new FileOutputStream(destinationPath);
// write the compressed bitmap at the destination specified by destinationPath.
decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight)
.compress(compressFormat, quality, fileOutputStream);
} finally {
if (fileOutputStream != null) {
fileOutputStream.flush();
fileOutputStream.close();
}
}
return new File(destinationPath);
}
static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight)
throws IOException {
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
//check the rotation of the image and display it properly
ExifInterface exif;
exif = new ExifInterface(imageFile.getAbsolutePath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
Matrix matrix = new Matrix();
if (orientation == 6) {
matrix.postRotate(90);
} else if (orientation == 3) {
matrix.postRotate(180);
} else if (orientation == 8) {
matrix.postRotate(270);
}
scaledBitmap = Bitmap
.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix,
true);
return scaledBitmap;
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
@NonNull
public static Bitmap getResizedBitmap(@NonNull Bitmap image, int maxSize) {
int width = image.getWidth();
int height = image.getHeight();
float bitmapRatio = (float) width / (float) height;
if (bitmapRatio > 1) {
width = maxSize;
height = (int) (width / bitmapRatio);
} else {
height = maxSize;
width = (int) (height * bitmapRatio);
}
return Bitmap.createScaledBitmap(image, width, height, true);
}
public static Bitmap resize(InputStream stream, int scaledWidth, int scaledHeight) {
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
}
public static Bitmap resize(InputStream stream, int scaledWidth, int scaledHeight) {
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
}
}

View file

@ -15,10 +15,8 @@
package code.name.monkey.retromusic.util;
import android.util.Base64;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@ -28,110 +26,109 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* Created by hefuyi on 2016/11/8.
*/
/** Created by hefuyi on 2016/11/8. */
public class LyricUtil {
private static final String lrcRootPath = android.os.Environment
.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/";
private static final String TAG = "LyricUtil";
private static final String lrcRootPath =
android.os.Environment.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/";
private static final String TAG = "LyricUtil";
@Nullable
public static File writeLrcToLoc(@NonNull String title, @NonNull String artist, @NonNull String lrcContext) {
FileWriter writer = null;
try {
File file = new File(getLrcPath(title, artist));
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
writer = new FileWriter(getLrcPath(title, artist));
writer.write(lrcContext);
return file;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if (writer != null)
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Nullable
public static File writeLrcToLoc(
@NonNull String title, @NonNull String artist, @NonNull String lrcContext) {
FileWriter writer = null;
try {
File file = new File(getLrcPath(title, artist));
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
writer = new FileWriter(getLrcPath(title, artist));
writer.write(lrcContext);
return file;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if (writer != null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
return file.delete();
}
public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
return file.delete();
}
public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
return file.exists();
}
public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
return file.exists();
}
public static boolean isLrcOriginalFileExist(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
return file.exists();
}
public static boolean isLrcOriginalFileExist(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
return file.exists();
}
@Nullable
public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
if (file.exists()) {
return file;
} else {
return null;
}
@Nullable
public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
if (file.exists()) {
return file;
} else {
return null;
}
}
@Nullable
public static File getLocalLyricOriginalFile(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
if (file.exists()) {
return file;
} else {
return null;
}
@Nullable
public static File getLocalLyricOriginalFile(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
if (file.exists()) {
return file;
} else {
return null;
}
}
private static String getLrcPath(String title, String artist) {
return lrcRootPath + title + " - " + artist + ".lrc";
}
private static String getLrcPath(String title, String artist) {
return lrcRootPath + title + " - " + artist + ".lrc";
}
private static String getLrcOriginalPath(String filePath) {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc");
}
private static String getLrcOriginalPath(String filePath) {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc");
}
@NonNull
public static String decryptBASE64(@NonNull String str) {
if (str == null || str.length() == 0) {
return null;
}
byte[] encode = str.getBytes(StandardCharsets.UTF_8);
// base64 解密
return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8);
@NonNull
public static String decryptBASE64(@NonNull String str) {
if (str == null || str.length() == 0) {
return null;
}
byte[] encode = str.getBytes(StandardCharsets.UTF_8);
// base64 解密
return new String(
Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8);
}
@NonNull
public static String getStringFromFile(@NonNull String title, @NonNull String artist) throws Exception {
File file = new File(getLrcPath(title, artist));
FileInputStream fin = new FileInputStream(file);
String ret = convertStreamToString(fin);
fin.close();
return ret;
}
@NonNull
public static String getStringFromFile(@NonNull String title, @NonNull String artist)
throws Exception {
File file = new File(getLrcPath(title, artist));
FileInputStream fin = new FileInputStream(file);
String ret = convertStreamToString(fin);
fin.close();
return ret;
}
private static String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
return sb.toString();
private static String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
return sb.toString();
}
}

View file

@ -21,12 +21,8 @@ import android.content.Context;
import android.content.Intent;
import android.media.audiofx.AudioEffect;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import org.jetbrains.annotations.NotNull;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.DriveModeActivity;
import code.name.monkey.retromusic.activities.LicenseActivity;
@ -38,71 +34,74 @@ import code.name.monkey.retromusic.activities.UserInfoActivity;
import code.name.monkey.retromusic.activities.WhatsNewActivity;
import code.name.monkey.retromusic.activities.bugreport.BugReportActivity;
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import org.jetbrains.annotations.NotNull;
public class NavigationUtil {
public static void bugReport(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, BugReportActivity.class), null);
public static void bugReport(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, BugReportActivity.class), null);
}
public static void goToLyrics(@NonNull Activity activity) {
Intent intent = new Intent(activity, LyricsActivity.class);
ActivityCompat.startActivity(activity, intent, null);
}
public static void goToOpenSource(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, LicenseActivity.class), null);
}
public static void goToPlayingQueue(@NonNull Activity activity) {
Intent intent = new Intent(activity, PlayingQueueActivity.class);
ActivityCompat.startActivity(activity, intent, null);
}
public static void goToProVersion(@NonNull Context context) {
ActivityCompat.startActivity(context, new Intent(context, PurchaseActivity.class), null);
}
public static void goToSupportDevelopment(@NonNull Activity activity) {
ActivityCompat.startActivity(
activity, new Intent(activity, SupportDevelopmentActivity.class), null);
}
public static void goToUserInfo(
@NonNull Activity activity, @NonNull ActivityOptions activityOptions) {
ActivityCompat.startActivity(
activity, new Intent(activity, UserInfoActivity.class), activityOptions.toBundle());
}
public static void gotoDriveMode(@NotNull final Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, DriveModeActivity.class), null);
}
public static void gotoWhatNews(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, WhatsNewActivity.class), null);
}
public static void openEqualizer(@NonNull final Activity activity) {
stockEqalizer(activity);
}
private static void stockEqalizer(@NonNull Activity activity) {
final int sessionId = MusicPlayerRemote.INSTANCE.getAudioSessionId();
if (sessionId == AudioEffect.ERROR_BAD_VALUE) {
Toast.makeText(
activity, activity.getResources().getString(R.string.no_audio_ID), Toast.LENGTH_LONG)
.show();
} else {
try {
final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
effects.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
effects.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
activity.startActivityForResult(effects, 0);
} catch (@NonNull final ActivityNotFoundException notFound) {
Toast.makeText(
activity,
activity.getResources().getString(R.string.no_equalizer),
Toast.LENGTH_SHORT)
.show();
}
}
public static void goToLyrics(@NonNull Activity activity) {
Intent intent = new Intent(activity, LyricsActivity.class);
ActivityCompat.startActivity(activity, intent, null);
}
public static void goToOpenSource(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, LicenseActivity.class), null);
}
public static void goToPlayingQueue(@NonNull Activity activity) {
Intent intent = new Intent(activity, PlayingQueueActivity.class);
ActivityCompat.startActivity(activity, intent, null);
}
public static void goToProVersion(@NonNull Context context) {
ActivityCompat.startActivity(context, new Intent(context, PurchaseActivity.class), null);
}
public static void goToSupportDevelopment(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, SupportDevelopmentActivity.class), null);
}
public static void goToUserInfo(@NonNull Activity activity,
@NonNull ActivityOptions activityOptions) {
ActivityCompat.startActivity(activity, new Intent(activity, UserInfoActivity.class),
activityOptions.toBundle());
}
public static void gotoDriveMode(@NotNull final Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, DriveModeActivity.class), null);
}
public static void gotoWhatNews(@NonNull Activity activity) {
ActivityCompat.startActivity(activity, new Intent(activity, WhatsNewActivity.class), null);
}
public static void openEqualizer(@NonNull final Activity activity) {
stockEqalizer(activity);
}
private static void stockEqalizer(@NonNull Activity activity) {
final int sessionId = MusicPlayerRemote.INSTANCE.getAudioSessionId();
if (sessionId == AudioEffect.ERROR_BAD_VALUE) {
Toast.makeText(activity, activity.getResources().getString(R.string.no_audio_ID),
Toast.LENGTH_LONG).show();
} else {
try {
final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
effects.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
effects.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
activity.startActivityForResult(effects, 0);
} catch (@NonNull final ActivityNotFoundException notFound) {
Toast.makeText(activity, activity.getResources().getString(R.string.no_equalizer),
Toast.LENGTH_SHORT).show();
}
}
}
}
}

View file

@ -14,6 +14,8 @@
package code.name.monkey.retromusic.util;
import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@ -23,259 +25,306 @@ import android.os.Environment;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.db.PlaylistWithSongs;
import code.name.monkey.retromusic.helper.M3UWriter;
import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.PlaylistSong;
import code.name.monkey.retromusic.model.Song;
import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PlaylistsUtil {
public static long createPlaylist(@NonNull final Context context, @Nullable final String name) {
int id = -1;
if (name != null && name.length() > 0) {
try {
Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.Playlists._ID},
MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name},
null);
if (cursor == null || cursor.getCount() < 1) {
final ContentValues values = new ContentValues(1);
values.put(MediaStore.Audio.PlaylistsColumns.NAME, name);
final Uri uri = context.getContentResolver().insert(
EXTERNAL_CONTENT_URI,
values);
if (uri != null) {
// Necessary because somehow the MediaStoreObserver is not notified when adding a playlist
context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
Toast.makeText(context, context.getResources().getString(
R.string.created_playlist_x, name), Toast.LENGTH_SHORT).show();
id = Integer.parseInt(uri.getLastPathSegment());
}
} else {
// Playlist exists
if (cursor.moveToFirst()) {
id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID));
}
}
if (cursor != null) {
cursor.close();
}
} catch (SecurityException e) {
e.printStackTrace();
}
}
if (id == -1) {
Toast.makeText(context, context.getResources().getString(
R.string.could_not_create_playlist), Toast.LENGTH_SHORT).show();
}
return id;
}
public static void deletePlaylists(@NonNull final Context context, @NonNull final List<Playlist> playlists) {
final StringBuilder selection = new StringBuilder();
selection.append(MediaStore.Audio.Playlists._ID + " IN (");
for (int i = 0; i < playlists.size(); i++) {
selection.append(playlists.get(i).getId());
if (i < playlists.size() - 1) {
selection.append(",");
}
}
selection.append(")");
try {
context.getContentResolver().delete(EXTERNAL_CONTENT_URI, selection.toString(), null);
context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
} catch (SecurityException ignored) {
}
}
public static void addToPlaylist(@NonNull final Context context, final Song song, final long playlistId, final boolean showToastOnFinish) {
List<Song> helperList = new ArrayList<>();
helperList.add(song);
addToPlaylist(context, helperList, playlistId, showToastOnFinish);
}
public static void addToPlaylist(@NonNull final Context context, @NonNull final List<Song> songs, final long playlistId, final boolean showToastOnFinish) {
final int size = songs.size();
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{
"max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")",
};
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
Cursor cursor = null;
int base = 0;
try {
try {
cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
base = cursor.getInt(0) + 1;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
int numInserted = 0;
for (int offSet = 0; offSet < size; offSet += 1000)
numInserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base));
if (showToastOnFinish) {
Toast.makeText(context, context.getResources().getString(
R.string.inserted_x_songs_into_playlist_x, numInserted, getNameForPlaylist(context, playlistId)), Toast.LENGTH_SHORT).show();
}
} catch (SecurityException ignored) {
ignored.printStackTrace();
}
}
@NonNull
public static ContentValues[] makeInsertItems(@NonNull final List<Song> songs, final int offset, int len, final int base) {
if (offset + len > songs.size()) {
len = songs.size() - offset;
}
ContentValues[] contentValues = new ContentValues[len];
for (int i = 0; i < len; i++) {
contentValues[i] = new ContentValues();
contentValues[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);
contentValues[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(offset + i).getId());
}
return contentValues;
}
public static String getNameForPlaylist(@NonNull final Context context, final long id) {
try {
Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.PlaylistsColumns.NAME},
BaseColumns._ID + "=?",
new String[]{String.valueOf(id)},
public static long createPlaylist(@NonNull final Context context, @Nullable final String name) {
int id = -1;
if (name != null && name.length() > 0) {
try {
Cursor cursor =
context
.getContentResolver()
.query(
EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.Playlists._ID},
MediaStore.Audio.PlaylistsColumns.NAME + "=?",
new String[] {name},
null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return cursor.getString(0);
}
} finally {
cursor.close();
}
}
} catch (SecurityException ignored) {
}
return "";
}
public static void removeFromPlaylist(@NonNull final Context context, @NonNull final Song song, long playlistId) {
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
"external", playlistId);
String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?";
String[] selectionArgs = new String[]{String.valueOf(song.getId())};
try {
context.getContentResolver().delete(uri, selection, selectionArgs);
} catch (SecurityException ignored) {
}
}
public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List<PlaylistSong> songs) {
final long playlistId = songs.get(0).getPlaylistId();
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
"external", playlistId);
String[] selectionArgs = new String[songs.size()];
for (int i = 0; i < selectionArgs.length; i++) {
selectionArgs[i] = String.valueOf(songs.get(i).getIdInPlayList());
}
String selection = MediaStore.Audio.Playlists.Members._ID + " in (";
//noinspection unused
for (String selectionArg : selectionArgs) selection += "?, ";
selection = selection.substring(0, selection.length() - 2) + ")";
try {
context.getContentResolver().delete(uri, selection, selectionArgs);
} catch (SecurityException ignored) {
}
}
public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final long songId) {
if (playlistId != -1) {
try {
Cursor c = context.getContentResolver().query(
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null);
int count = 0;
if (c != null) {
count = c.getCount();
c.close();
}
return count > 0;
} catch (SecurityException ignored) {
}
}
return false;
}
public static boolean moveItem(@NonNull final Context context, long playlistId, int from, int to) {
return MediaStore.Audio.Playlists.Members.moveItem(context.getContentResolver(),
playlistId, from, to);
}
public static void renamePlaylist(@NonNull final Context context, final long id, final String newName) {
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName);
try {
context.getContentResolver().update(EXTERNAL_CONTENT_URI,
contentValues,
MediaStore.Audio.Playlists._ID + "=?",
new String[]{String.valueOf(id)});
if (cursor == null || cursor.getCount() < 1) {
final ContentValues values = new ContentValues(1);
values.put(MediaStore.Audio.PlaylistsColumns.NAME, name);
final Uri uri = context.getContentResolver().insert(EXTERNAL_CONTENT_URI, values);
if (uri != null) {
// Necessary because somehow the MediaStoreObserver is not notified when adding a
// playlist
context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
} catch (SecurityException ignored) {
Toast.makeText(
context,
context.getResources().getString(R.string.created_playlist_x, name),
Toast.LENGTH_SHORT)
.show();
id = Integer.parseInt(uri.getLastPathSegment());
}
} else {
// Playlist exists
if (cursor.moveToFirst()) {
id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID));
}
}
}
public static File savePlaylist(Context context, Playlist playlist) throws IOException {
return M3UWriter.write(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
}
public static File savePlaylistWithSongs(PlaylistWithSongs playlist) throws IOException {
return M3UWriter.writeIO(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
}
public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) {
return playlistId != -1 && doesPlaylistExist(context,
MediaStore.Audio.Playlists._ID + "=?",
new String[]{String.valueOf(playlistId)});
}
public static boolean doesPlaylistExist(@NonNull final Context context, final String name) {
return doesPlaylistExist(context,
MediaStore.Audio.PlaylistsColumns.NAME + "=?",
new String[]{name});
}
private static boolean doesPlaylistExist(@NonNull Context context, @NonNull final String selection, @NonNull final String[] values) {
Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI,
new String[]{}, selection, values, null);
boolean exists = false;
if (cursor != null) {
exists = cursor.getCount() != 0;
cursor.close();
cursor.close();
}
return exists;
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
if (id == -1) {
Toast.makeText(
context,
context.getResources().getString(R.string.could_not_create_playlist),
Toast.LENGTH_SHORT)
.show();
}
return id;
}
public static void deletePlaylists(
@NonNull final Context context, @NonNull final List<Playlist> playlists) {
final StringBuilder selection = new StringBuilder();
selection.append(MediaStore.Audio.Playlists._ID + " IN (");
for (int i = 0; i < playlists.size(); i++) {
selection.append(playlists.get(i).getId());
if (i < playlists.size() - 1) {
selection.append(",");
}
}
selection.append(")");
try {
context.getContentResolver().delete(EXTERNAL_CONTENT_URI, selection.toString(), null);
context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
} catch (SecurityException ignored) {
}
}
public static void addToPlaylist(
@NonNull final Context context,
final Song song,
final long playlistId,
final boolean showToastOnFinish) {
List<Song> helperList = new ArrayList<>();
helperList.add(song);
addToPlaylist(context, helperList, playlistId, showToastOnFinish);
}
public static void addToPlaylist(
@NonNull final Context context,
@NonNull final List<Song> songs,
final long playlistId,
final boolean showToastOnFinish) {
final int size = songs.size();
final ContentResolver resolver = context.getContentResolver();
final String[] projection =
new String[] {
"max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")",
};
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
Cursor cursor = null;
int base = 0;
try {
try {
cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
base = cursor.getInt(0) + 1;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
int numInserted = 0;
for (int offSet = 0; offSet < size; offSet += 1000)
numInserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base));
if (showToastOnFinish) {
Toast.makeText(
context,
context
.getResources()
.getString(
R.string.inserted_x_songs_into_playlist_x,
numInserted,
getNameForPlaylist(context, playlistId)),
Toast.LENGTH_SHORT)
.show();
}
} catch (SecurityException ignored) {
ignored.printStackTrace();
}
}
@NonNull
public static ContentValues[] makeInsertItems(
@NonNull final List<Song> songs, final int offset, int len, final int base) {
if (offset + len > songs.size()) {
len = songs.size() - offset;
}
ContentValues[] contentValues = new ContentValues[len];
for (int i = 0; i < len; i++) {
contentValues[i] = new ContentValues();
contentValues[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);
contentValues[i].put(
MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(offset + i).getId());
}
return contentValues;
}
public static String getNameForPlaylist(@NonNull final Context context, final long id) {
try {
Cursor cursor =
context
.getContentResolver()
.query(
EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.PlaylistsColumns.NAME},
BaseColumns._ID + "=?",
new String[] {String.valueOf(id)},
null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return cursor.getString(0);
}
} finally {
cursor.close();
}
}
} catch (SecurityException ignored) {
}
return "";
}
public static void removeFromPlaylist(
@NonNull final Context context, @NonNull final Song song, long playlistId) {
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?";
String[] selectionArgs = new String[] {String.valueOf(song.getId())};
try {
context.getContentResolver().delete(uri, selection, selectionArgs);
} catch (SecurityException ignored) {
}
}
public static void removeFromPlaylist(
@NonNull final Context context, @NonNull final List<PlaylistSong> songs) {
final long playlistId = songs.get(0).getPlaylistId();
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
String[] selectionArgs = new String[songs.size()];
for (int i = 0; i < selectionArgs.length; i++) {
selectionArgs[i] = String.valueOf(songs.get(i).getIdInPlayList());
}
String selection = MediaStore.Audio.Playlists.Members._ID + " in (";
//noinspection unused
for (String selectionArg : selectionArgs) selection += "?, ";
selection = selection.substring(0, selection.length() - 2) + ")";
try {
context.getContentResolver().delete(uri, selection, selectionArgs);
} catch (SecurityException ignored) {
}
}
public static boolean doPlaylistContains(
@NonNull final Context context, final long playlistId, final long songId) {
if (playlistId != -1) {
try {
Cursor c =
context
.getContentResolver()
.query(
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
new String[] {MediaStore.Audio.Playlists.Members.AUDIO_ID},
MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?",
new String[] {String.valueOf(songId)},
null);
int count = 0;
if (c != null) {
count = c.getCount();
c.close();
}
return count > 0;
} catch (SecurityException ignored) {
}
}
return false;
}
public static boolean moveItem(
@NonNull final Context context, long playlistId, int from, int to) {
return MediaStore.Audio.Playlists.Members.moveItem(
context.getContentResolver(), playlistId, from, to);
}
public static void renamePlaylist(
@NonNull final Context context, final long id, final String newName) {
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName);
try {
context
.getContentResolver()
.update(
EXTERNAL_CONTENT_URI,
contentValues,
MediaStore.Audio.Playlists._ID + "=?",
new String[] {String.valueOf(id)});
context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
} catch (SecurityException ignored) {
}
}
public static File savePlaylist(Context context, Playlist playlist) throws IOException {
return M3UWriter.write(
new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
}
public static File savePlaylistWithSongs(PlaylistWithSongs playlist) throws IOException {
return M3UWriter.writeIO(
new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
}
public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) {
return playlistId != -1
&& doesPlaylistExist(
context,
MediaStore.Audio.Playlists._ID + "=?",
new String[] {String.valueOf(playlistId)});
}
public static boolean doesPlaylistExist(@NonNull final Context context, final String name) {
return doesPlaylistExist(
context, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[] {name});
}
private static boolean doesPlaylistExist(
@NonNull Context context, @NonNull final String selection, @NonNull final String[] values) {
Cursor cursor =
context
.getContentResolver()
.query(EXTERNAL_CONTENT_URI, new String[] {}, selection, values, null);
boolean exists = false;
if (cursor != null) {
exists = cursor.getCount() != 0;
cursor.close();
}
return exists;
}
}

View file

@ -18,203 +18,204 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.palette.graphics.Palette;
import code.name.monkey.appthemehelper.util.ColorUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import code.name.monkey.appthemehelper.util.ColorUtil;
public class RetroColorUtil {
public static int desaturateColor(int color, float ratio) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
public static int desaturateColor(int color, float ratio) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[1] = (hsv[1] / 1 * ratio) + (0.2f * (1.0f - ratio));
hsv[1] = (hsv[1] / 1 * ratio) + (0.2f * (1.0f - ratio));
return Color.HSVToColor(hsv);
return Color.HSVToColor(hsv);
}
@Nullable
public static Palette generatePalette(@Nullable Bitmap bitmap) {
return bitmap == null ? null : Palette.from(bitmap).clearFilters().generate();
}
public static int getTextColor(@Nullable Palette palette) {
if (palette == null) {
return -1;
}
@Nullable
public static Palette generatePalette(@Nullable Bitmap bitmap) {
return bitmap == null ? null : Palette.from(bitmap).clearFilters().generate();
int inverse = -1;
if (palette.getVibrantSwatch() != null) {
inverse = palette.getVibrantSwatch().getRgb();
} else if (palette.getLightVibrantSwatch() != null) {
inverse = palette.getLightVibrantSwatch().getRgb();
} else if (palette.getDarkVibrantSwatch() != null) {
inverse = palette.getDarkVibrantSwatch().getRgb();
}
public static int getTextColor(@Nullable Palette palette) {
if (palette == null) {
return -1;
}
int background = getSwatch(palette).getRgb();
int inverse = -1;
if (palette.getVibrantSwatch() != null) {
inverse = palette.getVibrantSwatch().getRgb();
} else if (palette.getLightVibrantSwatch() != null) {
inverse = palette.getLightVibrantSwatch().getRgb();
} else if (palette.getDarkVibrantSwatch() != null) {
inverse = palette.getDarkVibrantSwatch().getRgb();
}
int background = getSwatch(palette).getRgb();
if (inverse != -1) {
return ColorUtil.INSTANCE.getReadableText(inverse, background, 150);
}
return ColorUtil.INSTANCE.stripAlpha(getSwatch(palette).getTitleTextColor());
if (inverse != -1) {
return ColorUtil.INSTANCE.getReadableText(inverse, background, 150);
}
return ColorUtil.INSTANCE.stripAlpha(getSwatch(palette).getTitleTextColor());
}
@NonNull
public static Palette.Swatch getSwatch(@Nullable Palette palette) {
if (palette == null) {
return new Palette.Swatch(Color.WHITE, 1);
}
@NonNull
public static Palette.Swatch getSwatch(@Nullable Palette palette) {
if (palette == null) {
return new Palette.Swatch(Color.WHITE, 1);
}
return getBestPaletteSwatchFrom(palette.getSwatches());
}
public static int getMatColor(Context context, String typeColor) {
int returnColor = Color.BLACK;
int arrayId =
context
.getResources()
.getIdentifier(
"md_" + typeColor, "array", context.getApplicationContext().getPackageName());
if (arrayId != 0) {
TypedArray colors = context.getResources().obtainTypedArray(arrayId);
int index = (int) (Math.random() * colors.length());
returnColor = colors.getColor(index, Color.BLACK);
colors.recycle();
}
return returnColor;
}
@ColorInt
public static int getColor(@Nullable Palette palette, int fallback) {
if (palette != null) {
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch().getRgb();
} else if (palette.getDarkVibrantSwatch() != null) {
return palette.getDarkVibrantSwatch().getRgb();
} else if (palette.getLightVibrantSwatch() != null) {
return palette.getLightVibrantSwatch().getRgb();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch().getRgb();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch().getRgb();
} else if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch().getRgb();
} else if (!palette.getSwatches().isEmpty()) {
return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb();
}
}
return fallback;
}
private static Palette.Swatch getTextSwatch(@Nullable Palette palette) {
if (palette == null) {
return new Palette.Swatch(Color.BLACK, 1);
}
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch();
} else {
return new Palette.Swatch(Color.BLACK, 1);
}
}
@ColorInt
public static int getBackgroundColor(@Nullable Palette palette) {
return getProperBackgroundSwatch(palette).getRgb();
}
private static Palette.Swatch getProperBackgroundSwatch(@Nullable Palette palette) {
if (palette == null) {
return new Palette.Swatch(Color.BLACK, 1);
}
if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch();
} else {
return new Palette.Swatch(Color.BLACK, 1);
}
}
private static Palette.Swatch getBestPaletteSwatchFrom(Palette palette) {
if (palette != null) {
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch();
} else if (palette.getDarkVibrantSwatch() != null) {
return palette.getDarkVibrantSwatch();
} else if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch();
} else if (palette.getLightVibrantSwatch() != null) {
return palette.getLightVibrantSwatch();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch();
} else if (!palette.getSwatches().isEmpty()) {
return getBestPaletteSwatchFrom(palette.getSwatches());
}
}
return null;
}
public static int getMatColor(Context context, String typeColor) {
int returnColor = Color.BLACK;
int arrayId = context.getResources().getIdentifier("md_" + typeColor, "array",
context.getApplicationContext().getPackageName());
if (arrayId != 0) {
TypedArray colors = context.getResources().obtainTypedArray(arrayId);
int index = (int) (Math.random() * colors.length());
returnColor = colors.getColor(index, Color.BLACK);
colors.recycle();
}
return returnColor;
private static Palette.Swatch getBestPaletteSwatchFrom(List<Palette.Swatch> swatches) {
if (swatches == null) {
return null;
}
@ColorInt
public static int getColor(@Nullable Palette palette, int fallback) {
if (palette != null) {
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch().getRgb();
} else if (palette.getDarkVibrantSwatch() != null) {
return palette.getDarkVibrantSwatch().getRgb();
} else if (palette.getLightVibrantSwatch() != null) {
return palette.getLightVibrantSwatch().getRgb();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch().getRgb();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch().getRgb();
} else if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch().getRgb();
} else if (!palette.getSwatches().isEmpty()) {
return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb();
}
}
return fallback;
}
private static Palette.Swatch getTextSwatch(@Nullable Palette palette) {
if (palette == null) {
return new Palette.Swatch(Color.BLACK, 1);
}
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch();
} else {
return new Palette.Swatch(Color.BLACK, 1);
}
}
@ColorInt
public static int getBackgroundColor(@Nullable Palette palette) {
return getProperBackgroundSwatch(palette).getRgb();
}
private static Palette.Swatch getProperBackgroundSwatch(@Nullable Palette palette) {
if (palette == null) {
return new Palette.Swatch(Color.BLACK, 1);
}
if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch();
} else {
return new Palette.Swatch(Color.BLACK, 1);
}
}
private static Palette.Swatch getBestPaletteSwatchFrom(Palette palette) {
if (palette != null) {
if (palette.getVibrantSwatch() != null) {
return palette.getVibrantSwatch();
} else if (palette.getMutedSwatch() != null) {
return palette.getMutedSwatch();
} else if (palette.getDarkVibrantSwatch() != null) {
return palette.getDarkVibrantSwatch();
} else if (palette.getDarkMutedSwatch() != null) {
return palette.getDarkMutedSwatch();
} else if (palette.getLightVibrantSwatch() != null) {
return palette.getLightVibrantSwatch();
} else if (palette.getLightMutedSwatch() != null) {
return palette.getLightMutedSwatch();
} else if (!palette.getSwatches().isEmpty()) {
return getBestPaletteSwatchFrom(palette.getSwatches());
}
}
return null;
}
private static Palette.Swatch getBestPaletteSwatchFrom(List<Palette.Swatch> swatches) {
if (swatches == null) {
return null;
}
return Collections.max(swatches, (opt1, opt2) -> {
int a = opt1 == null ? 0 : opt1.getPopulation();
int b = opt2 == null ? 0 : opt2.getPopulation();
return a - b;
return Collections.max(
swatches,
(opt1, opt2) -> {
int a = opt1 == null ? 0 : opt1.getPopulation();
int b = opt2 == null ? 0 : opt2.getPopulation();
return a - b;
});
}
public static int getDominantColor(Bitmap bitmap, int defaultFooterColor) {
List<Palette.Swatch> swatchesTemp = Palette.from(bitmap).generate().getSwatches();
List<Palette.Swatch> swatches = new ArrayList<Palette.Swatch>(swatchesTemp);
Collections.sort(
swatches, (swatch1, swatch2) -> swatch2.getPopulation() - swatch1.getPopulation());
return swatches.size() > 0 ? swatches.get(0).getRgb() : defaultFooterColor;
}
@ColorInt
public static int shiftBackgroundColorForLightText(@ColorInt int backgroundColor) {
while (ColorUtil.INSTANCE.isColorLight(backgroundColor)) {
backgroundColor = ColorUtil.INSTANCE.darkenColor(backgroundColor);
}
return backgroundColor;
}
@ColorInt
public static int shiftBackgroundColorForDarkText(@ColorInt int backgroundColor) {
int color = backgroundColor;
while (!ColorUtil.INSTANCE.isColorLight(backgroundColor)) {
color = ColorUtil.INSTANCE.lightenColor(backgroundColor);
}
return color;
}
private static class SwatchComparator implements Comparator<Palette.Swatch> {
private static SwatchComparator sInstance;
static SwatchComparator getInstance() {
if (sInstance == null) {
sInstance = new SwatchComparator();
}
return sInstance;
}
public static int getDominantColor(Bitmap bitmap, int defaultFooterColor) {
List<Palette.Swatch> swatchesTemp = Palette.from(bitmap).generate().getSwatches();
List<Palette.Swatch> swatches = new ArrayList<Palette.Swatch>(swatchesTemp);
Collections.sort(swatches, (swatch1, swatch2) -> swatch2.getPopulation() - swatch1.getPopulation());
return swatches.size() > 0 ? swatches.get(0).getRgb() : defaultFooterColor;
}
@ColorInt
public static int shiftBackgroundColorForLightText(@ColorInt int backgroundColor) {
while (ColorUtil.INSTANCE.isColorLight(backgroundColor)) {
backgroundColor = ColorUtil.INSTANCE.darkenColor(backgroundColor);
}
return backgroundColor;
}
@ColorInt
public static int shiftBackgroundColorForDarkText(@ColorInt int backgroundColor) {
int color = backgroundColor;
while (!ColorUtil.INSTANCE.isColorLight(backgroundColor)) {
color = ColorUtil.INSTANCE.lightenColor(backgroundColor);
}
return color;
}
private static class SwatchComparator implements Comparator<Palette.Swatch> {
private static SwatchComparator sInstance;
static SwatchComparator getInstance() {
if (sInstance == null) {
sInstance = new SwatchComparator();
}
return sInstance;
}
@Override
public int compare(Palette.Swatch lhs, Palette.Swatch rhs) {
return lhs.getPopulation() - rhs.getPopulation();
}
@Override
public int compare(Palette.Swatch lhs, Palette.Swatch rhs) {
return lhs.getPopulation() - rhs.getPopulation();
}
}
}

View file

@ -35,165 +35,176 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import java.text.DecimalFormat;
import code.name.monkey.appthemehelper.util.TintHelper;
import code.name.monkey.retromusic.App;
import java.text.DecimalFormat;
public class RetroUtil {
private static final int[] TEMP_ARRAY = new int[1];
private static final int[] TEMP_ARRAY = new int[1];
private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar";
private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar";
public static int calculateNoOfColumns(@NonNull Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
return (int) (dpWidth / 180);
public static int calculateNoOfColumns(@NonNull Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
return (int) (dpWidth / 180);
}
@NonNull
public static Bitmap createBitmap(@NonNull Drawable drawable, float sizeMultiplier) {
Bitmap bitmap =
Bitmap.createBitmap(
(int) (drawable.getIntrinsicWidth() * sizeMultiplier),
(int) (drawable.getIntrinsicHeight() * sizeMultiplier),
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
drawable.draw(c);
return bitmap;
}
public static String formatValue(float value) {
String[] arr = {"", "K", "M", "B", "T", "P", "E"};
int index = 0;
while ((value / 1000) >= 1) {
value = value / 1000;
index++;
}
DecimalFormat decimalFormat = new DecimalFormat("#.##");
return String.format("%s %s", decimalFormat.format(value), arr[index]);
}
@NonNull
public static Bitmap createBitmap(@NonNull Drawable drawable, float sizeMultiplier) {
Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier),
(int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
drawable.draw(c);
return bitmap;
public static float frequencyCount(int frequency) {
return (float) (frequency / 1000.0);
}
public static Point getScreenSize(@NonNull Context c) {
Display display = null;
if (c.getSystemService(Context.WINDOW_SERVICE) != null) {
display = ((WindowManager) c.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
}
Point size = new Point();
if (display != null) {
display.getSize(size);
}
return size;
}
public static String formatValue(float value) {
String[] arr = {"", "K", "M", "B", "T", "P", "E"};
int index = 0;
while ((value / 1000) >= 1) {
value = value / 1000;
index++;
public static int getStatusBarHeight() {
int result = 0;
int resourceId =
App.Companion.getContext()
.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = App.Companion.getContext().getResources().getDimensionPixelSize(resourceId);
}
return result;
}
@Nullable
public static Drawable getTintedVectorDrawable(
@NonNull Context context, @DrawableRes int id, @ColorInt int color) {
return TintHelper.createTintedDrawable(
getVectorDrawable(context.getResources(), id, context.getTheme()), color);
}
@Nullable
public static Drawable getTintedVectorDrawable(
@NonNull Resources res,
@DrawableRes int resId,
@Nullable Resources.Theme theme,
@ColorInt int color) {
return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color);
}
@Nullable
public static Drawable getVectorDrawable(
@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) {
if (Build.VERSION.SDK_INT >= 21) {
return res.getDrawable(resId, theme);
}
return VectorDrawableCompat.create(res, resId, theme);
}
public static void hideSoftKeyboard(@Nullable Activity activity) {
if (activity != null) {
View currentFocus = activity.getCurrentFocus();
if (currentFocus != null) {
InputMethodManager inputMethodManager =
(InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
}
DecimalFormat decimalFormat = new DecimalFormat("#.##");
return String.format("%s %s", decimalFormat.format(value), arr[index]);
}
}
}
public static float frequencyCount(int frequency) {
return (float) (frequency / 1000.0);
public static boolean isAllowedToDownloadMetadata(final @NonNull Context context) {
switch (PreferenceUtil.INSTANCE.getAutoDownloadImagesPolicy()) {
case "always":
return true;
case "only_wifi":
final ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
return netInfo != null
&& netInfo.getType() == ConnectivityManager.TYPE_WIFI
&& netInfo.isConnectedOrConnecting();
case "never":
default:
return false;
}
}
public static Point getScreenSize(@NonNull Context c) {
Display display = null;
if (c.getSystemService(Context.WINDOW_SERVICE) != null) {
display = ((WindowManager) c.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
}
Point size = new Point();
if (display != null) {
display.getSize(size);
}
return size;
}
public static boolean isLandscape() {
return App.Companion.getContext().getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
}
public static int getStatusBarHeight() {
int result = 0;
int resourceId = App.Companion.getContext().getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = App.Companion.getContext().getResources().getDimensionPixelSize(resourceId);
}
return result;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isRTL(@NonNull Context context) {
Configuration config = context.getResources().getConfiguration();
return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
@Nullable
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id,
@ColorInt int color) {
return TintHelper.createTintedDrawable(
getVectorDrawable(context.getResources(), id, context.getTheme()), color);
}
public static boolean isTablet() {
return App.Companion.getContext().getResources().getConfiguration().smallestScreenWidthDp
>= 600;
}
@Nullable
public static Drawable getTintedVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
@Nullable Resources.Theme theme, @ColorInt int color) {
return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color);
}
public static void openUrl(@NonNull Activity context, @NonNull String str) {
Intent intent = new Intent("android.intent.action.VIEW");
intent.setData(Uri.parse(str));
intent.setFlags(268435456);
context.startActivity(intent);
}
@Nullable
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
@Nullable Resources.Theme theme) {
if (Build.VERSION.SDK_INT >= 21) {
return res.getDrawable(resId, theme);
}
return VectorDrawableCompat.create(res, resId, theme);
}
public static void setAllowDrawUnderNavigationBar(Window window) {
window.setNavigationBarColor(Color.TRANSPARENT);
window
.getDecorView()
.setSystemUiVisibility(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
? View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
: View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
public static void hideSoftKeyboard(@Nullable Activity activity) {
if (activity != null) {
View currentFocus = activity.getCurrentFocus();
if (currentFocus != null) {
InputMethodManager inputMethodManager = (InputMethodManager) activity
.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
}
}
}
}
public static boolean isAllowedToDownloadMetadata(final @NonNull Context context) {
switch (PreferenceUtil.INSTANCE.getAutoDownloadImagesPolicy()) {
case "always":
return true;
case "only_wifi":
final ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
return netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo
.isConnectedOrConnecting();
case "never":
default:
return false;
}
}
public static boolean isLandscape() {
return App.Companion.getContext().getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isRTL(@NonNull Context context) {
Configuration config = context.getResources().getConfiguration();
return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
public static boolean isTablet() {
return App.Companion.getContext().getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
public static void openUrl(@NonNull Activity context, @NonNull String str) {
Intent intent = new Intent("android.intent.action.VIEW");
intent.setData(Uri.parse(str));
intent.setFlags(268435456);
context.startActivity(intent);
}
public static void setAllowDrawUnderNavigationBar(Window window) {
window.setNavigationBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION :
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
);
}
public static void setAllowDrawUnderStatusBar(@NonNull Window window) {
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
public static void setAllowDrawUnderStatusBar(@NonNull Window window) {
window
.getDecorView()
.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}

View file

@ -5,142 +5,142 @@ import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Build;
import android.util.StateSet;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
public class RippleUtils {
public static final boolean USE_FRAMEWORK_RIPPLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
private static final int[] PRESSED_STATE_SET = {
android.R.attr.state_pressed,
};
private static final int[] HOVERED_FOCUSED_STATE_SET = {
android.R.attr.state_hovered, android.R.attr.state_focused,
};
private static final int[] FOCUSED_STATE_SET = {
android.R.attr.state_focused,
};
private static final int[] HOVERED_STATE_SET = {
android.R.attr.state_hovered,
};
public static final boolean USE_FRAMEWORK_RIPPLE =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
private static final int[] PRESSED_STATE_SET = {
android.R.attr.state_pressed,
};
private static final int[] HOVERED_FOCUSED_STATE_SET = {
android.R.attr.state_hovered, android.R.attr.state_focused,
};
private static final int[] FOCUSED_STATE_SET = {
android.R.attr.state_focused,
};
private static final int[] HOVERED_STATE_SET = {
android.R.attr.state_hovered,
};
private static final int[] SELECTED_PRESSED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_pressed,
};
private static final int[] SELECTED_HOVERED_FOCUSED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_hovered, android.R.attr.state_focused,
};
private static final int[] SELECTED_FOCUSED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_focused,
};
private static final int[] SELECTED_HOVERED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_hovered,
};
private static final int[] SELECTED_STATE_SET = {
android.R.attr.state_selected,
};
private static final int[] SELECTED_PRESSED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_pressed,
};
private static final int[] SELECTED_HOVERED_FOCUSED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_hovered, android.R.attr.state_focused,
};
private static final int[] SELECTED_FOCUSED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_focused,
};
private static final int[] SELECTED_HOVERED_STATE_SET = {
android.R.attr.state_selected, android.R.attr.state_hovered,
};
private static final int[] SELECTED_STATE_SET = {
android.R.attr.state_selected,
};
private static final int[] ENABLED_PRESSED_STATE_SET = {
android.R.attr.state_enabled, android.R.attr.state_pressed
};
private static final int[] ENABLED_PRESSED_STATE_SET = {
android.R.attr.state_enabled, android.R.attr.state_pressed
};
public static ColorStateList convertToRippleDrawableColor(@Nullable ColorStateList rippleColor) {
if (USE_FRAMEWORK_RIPPLE) {
int size = 2;
public static ColorStateList convertToRippleDrawableColor(@Nullable ColorStateList rippleColor) {
if (USE_FRAMEWORK_RIPPLE) {
int size = 2;
final int[][] states = new int[size][];
final int[] colors = new int[size];
int i = 0;
final int[][] states = new int[size][];
final int[] colors = new int[size];
int i = 0;
// Ideally we would define a different composite color for each state, but that causes the
// ripple animation to abort prematurely.
// So we only allow two base states: selected, and non-selected. For each base state, we only
// base the ripple composite on its pressed state.
// Ideally we would define a different composite color for each state, but that causes the
// ripple animation to abort prematurely.
// So we only allow two base states: selected, and non-selected. For each base state, we only
// base the ripple composite on its pressed state.
// Selected base state.
states[i] = SELECTED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET);
i++;
// Selected base state.
states[i] = SELECTED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET);
i++;
// Non-selected base state.
states[i] = StateSet.NOTHING;
colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET);
i++;
// Non-selected base state.
states[i] = StateSet.NOTHING;
colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET);
i++;
return new ColorStateList(states, colors);
} else {
int size = 10;
return new ColorStateList(states, colors);
} else {
int size = 10;
final int[][] states = new int[size][];
final int[] colors = new int[size];
int i = 0;
final int[][] states = new int[size][];
final int[] colors = new int[size];
int i = 0;
states[i] = SELECTED_PRESSED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET);
i++;
states[i] = SELECTED_PRESSED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET);
i++;
states[i] = SELECTED_HOVERED_FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_FOCUSED_STATE_SET);
i++;
states[i] = SELECTED_HOVERED_FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_FOCUSED_STATE_SET);
i++;
states[i] = SELECTED_FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_FOCUSED_STATE_SET);
i++;
states[i] = SELECTED_FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_FOCUSED_STATE_SET);
i++;
states[i] = SELECTED_HOVERED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_STATE_SET);
i++;
states[i] = SELECTED_HOVERED_STATE_SET;
colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_STATE_SET);
i++;
// Checked state.
states[i] = SELECTED_STATE_SET;
colors[i] = Color.TRANSPARENT;
i++;
// Checked state.
states[i] = SELECTED_STATE_SET;
colors[i] = Color.TRANSPARENT;
i++;
states[i] = PRESSED_STATE_SET;
colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET);
i++;
states[i] = PRESSED_STATE_SET;
colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET);
i++;
states[i] = HOVERED_FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, HOVERED_FOCUSED_STATE_SET);
i++;
states[i] = HOVERED_FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, HOVERED_FOCUSED_STATE_SET);
i++;
states[i] = FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, FOCUSED_STATE_SET);
i++;
states[i] = FOCUSED_STATE_SET;
colors[i] = getColorForState(rippleColor, FOCUSED_STATE_SET);
i++;
states[i] = HOVERED_STATE_SET;
colors[i] = getColorForState(rippleColor, HOVERED_STATE_SET);
i++;
states[i] = HOVERED_STATE_SET;
colors[i] = getColorForState(rippleColor, HOVERED_STATE_SET);
i++;
// Default state.
states[i] = StateSet.NOTHING;
colors[i] = Color.TRANSPARENT;
i++;
// Default state.
states[i] = StateSet.NOTHING;
colors[i] = Color.TRANSPARENT;
i++;
return new ColorStateList(states, colors);
}
return new ColorStateList(states, colors);
}
}
@ColorInt
private static int getColorForState(@Nullable ColorStateList rippleColor, int[] state) {
int color;
if (rippleColor != null) {
color = rippleColor.getColorForState(state, rippleColor.getDefaultColor());
} else {
color = Color.TRANSPARENT;
}
return USE_FRAMEWORK_RIPPLE ? doubleAlpha(color) : color;
@ColorInt
private static int getColorForState(@Nullable ColorStateList rippleColor, int[] state) {
int color;
if (rippleColor != null) {
color = rippleColor.getColorForState(state, rippleColor.getDefaultColor());
} else {
color = Color.TRANSPARENT;
}
return USE_FRAMEWORK_RIPPLE ? doubleAlpha(color) : color;
}
/**
* On API 21+, the framework composites a ripple color onto the display at about 50% opacity.
* Since we are providing precise ripple colors, cancel that out by doubling the opacity here.
*/
@ColorInt
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static int doubleAlpha(@ColorInt int color) {
int alpha = Math.min(2 * Color.alpha(color), 255);
return ColorUtils.setAlphaComponent(color, alpha);
}
/**
* On API 21+, the framework composites a ripple color onto the display at about 50% opacity.
* Since we are providing precise ripple colors, cancel that out by doubling the opacity here.
*/
@ColorInt
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static int doubleAlpha(@ColorInt int color) {
int alpha = Math.min(2 * Color.alpha(color), 255);
return ColorUtils.setAlphaComponent(color, alpha);
}
}

View file

@ -26,278 +26,283 @@ import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.Fragment;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.generic.Utils;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.Song;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.Song;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.generic.Utils;
public class SAFUtil {
public static final String TAG = SAFUtil.class.getSimpleName();
public static final String SEPARATOR = "###/SAF/###";
public static final String TAG = SAFUtil.class.getSimpleName();
public static final String SEPARATOR = "###/SAF/###";
public static final int REQUEST_SAF_PICK_FILE = 42;
public static final int REQUEST_SAF_PICK_TREE = 43;
public static final int REQUEST_SAF_PICK_FILE = 42;
public static final int REQUEST_SAF_PICK_TREE = 43;
public static boolean isSAFRequired(File file) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !file.canWrite();
public static boolean isSAFRequired(File file) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !file.canWrite();
}
public static boolean isSAFRequired(String path) {
return isSAFRequired(new File(path));
}
public static boolean isSAFRequired(AudioFile audio) {
return isSAFRequired(audio.getFile());
}
public static boolean isSAFRequired(Song song) {
return isSAFRequired(song.getData());
}
public static boolean isSAFRequired(List<String> paths) {
for (String path : paths) {
if (isSAFRequired(path)) return true;
}
return false;
}
public static boolean isSAFRequiredForSongs(List<Song> songs) {
for (Song song : songs) {
if (isSAFRequired(song)) return true;
}
return false;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void openFilePicker(Activity activity) {
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("audio/*");
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void openFilePicker(Fragment fragment) {
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("audio/*");
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void openTreePicker(Activity activity) {
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void openTreePicker(Fragment fragment) {
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void saveTreeUri(Context context, Intent data) {
Uri uri = data.getData();
context
.getContentResolver()
.takePersistableUriPermission(
uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
PreferenceUtil.INSTANCE.setSafSdCardUri(uri.toString());
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isTreeUriSaved(Context context) {
return !TextUtils.isEmpty(PreferenceUtil.INSTANCE.getSafSdCardUri());
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isSDCardAccessGranted(Context context) {
if (!isTreeUriSaved(context)) return false;
String sdcardUri = PreferenceUtil.INSTANCE.getSafSdCardUri();
List<UriPermission> perms = context.getContentResolver().getPersistedUriPermissions();
for (UriPermission perm : perms) {
if (perm.getUri().toString().equals(sdcardUri) && perm.isWritePermission()) return true;
}
public static boolean isSAFRequired(String path) {
return isSAFRequired(new File(path));
return false;
}
/**
* https://github.com/vanilla-music/vanilla-music-tag-editor/commit/e00e87fef289f463b6682674aa54be834179ccf0#diff-d436417358d5dfbb06846746d43c47a5R359
* Finds needed file through Document API for SAF. It's not optimized yet - you can still gain
* wrong URI on files such as "/a/b/c.mp3" and "/b/a/c.mp3", but I consider it complete enough to
* be usable.
*
* @param dir - document file representing current dir of search
* @param segments - path segments that are left to find
* @return URI for found file. Null if nothing found.
*/
@Nullable
public static Uri findDocument(DocumentFile dir, List<String> segments) {
for (DocumentFile file : dir.listFiles()) {
int index = segments.indexOf(file.getName());
if (index == -1) {
continue;
}
if (file.isDirectory()) {
segments.remove(file.getName());
return findDocument(file, segments);
}
if (file.isFile() && index == segments.size() - 1) {
// got to the last part
return file.getUri();
}
}
public static boolean isSAFRequired(AudioFile audio) {
return isSAFRequired(audio.getFile());
return null;
}
public static void write(Context context, AudioFile audio, Uri safUri) {
if (isSAFRequired(audio)) {
writeSAF(context, audio, safUri);
} else {
try {
writeFile(audio);
} catch (CannotWriteException e) {
e.printStackTrace();
}
}
}
public static void writeFile(AudioFile audio) throws CannotWriteException {
audio.commit();
}
public static void writeSAF(Context context, AudioFile audio, Uri safUri) {
Uri uri = null;
if (context == null) {
Log.e(TAG, "writeSAF: context == null");
return;
}
public static boolean isSAFRequired(Song song) {
return isSAFRequired(song.getData());
if (isTreeUriSaved(context)) {
List<String> pathSegments =
new ArrayList<>(Arrays.asList(audio.getFile().getAbsolutePath().split("/")));
Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.getSafSdCardUri());
uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments);
}
public static boolean isSAFRequired(List<String> paths) {
for (String path : paths) {
if (isSAFRequired(path)) return true;
}
return false;
if (uri == null) {
uri = safUri;
}
public static boolean isSAFRequiredForSongs(List<Song> songs) {
for (Song song : songs) {
if (isSAFRequired(song)) return true;
}
return false;
if (uri == null) {
Log.e(TAG, "writeSAF: Can't get SAF URI");
toast(context, context.getString(R.string.saf_error_uri));
return;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void openFilePicker(Activity activity) {
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("audio/*");
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE);
try {
// copy file to app folder to use jaudiotagger
final File original = audio.getFile();
File temp = File.createTempFile("tmp-media", '.' + Utils.getExtension(original));
Utils.copy(original, temp);
temp.deleteOnExit();
audio.setFile(temp);
writeFile(audio);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw");
if (pfd == null) {
Log.e(TAG, "writeSAF: SAF provided incorrect URI: " + uri);
return;
}
// now read persisted data and write it to real FD provided by SAF
FileInputStream fis = new FileInputStream(temp);
byte[] audioContent = FileUtil.readBytes(fis);
FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
fos.write(audioContent);
fos.close();
temp.delete();
} catch (final Exception e) {
Log.e(TAG, "writeSAF: Failed to write to file descriptor provided by SAF", e);
toast(
context,
String.format(context.getString(R.string.saf_write_failed), e.getLocalizedMessage()));
}
}
public static void delete(Context context, String path, Uri safUri) {
if (isSAFRequired(path)) {
deleteSAF(context, path, safUri);
} else {
try {
deleteFile(path);
} catch (NullPointerException e) {
Log.e("MusicUtils", "Failed to find file " + path);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void deleteFile(String path) {
new File(path).delete();
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void deleteSAF(Context context, String path, Uri safUri) {
Uri uri = null;
if (context == null) {
Log.e(TAG, "deleteSAF: context == null");
return;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void openFilePicker(Fragment fragment) {
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("audio/*");
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE);
if (isTreeUriSaved(context)) {
List<String> pathSegments = new ArrayList<>(Arrays.asList(path.split("/")));
Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.getSafSdCardUri());
uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void openTreePicker(Activity activity) {
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE);
if (uri == null) {
uri = safUri;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void openTreePicker(Fragment fragment) {
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE);
if (uri == null) {
Log.e(TAG, "deleteSAF: Can't get SAF URI");
toast(context, context.getString(R.string.saf_error_uri));
return;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void saveTreeUri(Context context, Intent data) {
Uri uri = data.getData();
context.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
PreferenceUtil.INSTANCE.setSafSdCardUri(uri.toString());
try {
DocumentsContract.deleteDocument(context.getContentResolver(), uri);
} catch (final Exception e) {
Log.e(TAG, "deleteSAF: Failed to delete a file descriptor provided by SAF", e);
toast(
context,
String.format(context.getString(R.string.saf_delete_failed), e.getLocalizedMessage()));
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isTreeUriSaved(Context context) {
return !TextUtils.isEmpty(PreferenceUtil.INSTANCE.getSafSdCardUri());
private static void toast(final Context context, final String message) {
if (context instanceof Activity) {
((Activity) context)
.runOnUiThread(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isSDCardAccessGranted(Context context) {
if (!isTreeUriSaved(context)) return false;
String sdcardUri = PreferenceUtil.INSTANCE.getSafSdCardUri();
List<UriPermission> perms = context.getContentResolver().getPersistedUriPermissions();
for (UriPermission perm : perms) {
if (perm.getUri().toString().equals(sdcardUri) && perm.isWritePermission()) return true;
}
return false;
}
/**
* https://github.com/vanilla-music/vanilla-music-tag-editor/commit/e00e87fef289f463b6682674aa54be834179ccf0#diff-d436417358d5dfbb06846746d43c47a5R359
* Finds needed file through Document API for SAF. It's not optimized yet - you can still gain wrong URI on
* files such as "/a/b/c.mp3" and "/b/a/c.mp3", but I consider it complete enough to be usable.
*
* @param dir - document file representing current dir of search
* @param segments - path segments that are left to find
* @return URI for found file. Null if nothing found.
*/
@Nullable
public static Uri findDocument(DocumentFile dir, List<String> segments) {
for (DocumentFile file : dir.listFiles()) {
int index = segments.indexOf(file.getName());
if (index == -1) {
continue;
}
if (file.isDirectory()) {
segments.remove(file.getName());
return findDocument(file, segments);
}
if (file.isFile() && index == segments.size() - 1) {
// got to the last part
return file.getUri();
}
}
return null;
}
public static void write(Context context, AudioFile audio, Uri safUri) {
if (isSAFRequired(audio)) {
writeSAF(context, audio, safUri);
} else {
try {
writeFile(audio);
} catch (CannotWriteException e) {
e.printStackTrace();
}
}
}
public static void writeFile(AudioFile audio) throws CannotWriteException {
audio.commit();
}
public static void writeSAF(Context context, AudioFile audio, Uri safUri) {
Uri uri = null;
if (context == null) {
Log.e(TAG, "writeSAF: context == null");
return;
}
if (isTreeUriSaved(context)) {
List<String> pathSegments = new ArrayList<>(Arrays.asList(audio.getFile().getAbsolutePath().split("/")));
Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.getSafSdCardUri());
uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments);
}
if (uri == null) {
uri = safUri;
}
if (uri == null) {
Log.e(TAG, "writeSAF: Can't get SAF URI");
toast(context, context.getString(R.string.saf_error_uri));
return;
}
try {
// copy file to app folder to use jaudiotagger
final File original = audio.getFile();
File temp = File.createTempFile("tmp-media", '.' + Utils.getExtension(original));
Utils.copy(original, temp);
temp.deleteOnExit();
audio.setFile(temp);
writeFile(audio);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw");
if (pfd == null) {
Log.e(TAG, "writeSAF: SAF provided incorrect URI: " + uri);
return;
}
// now read persisted data and write it to real FD provided by SAF
FileInputStream fis = new FileInputStream(temp);
byte[] audioContent = FileUtil.readBytes(fis);
FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
fos.write(audioContent);
fos.close();
temp.delete();
} catch (final Exception e) {
Log.e(TAG, "writeSAF: Failed to write to file descriptor provided by SAF", e);
toast(context, String.format(context.getString(R.string.saf_write_failed), e.getLocalizedMessage()));
}
}
public static void delete(Context context, String path, Uri safUri) {
if (isSAFRequired(path)) {
deleteSAF(context, path, safUri);
} else {
try {
deleteFile(path);
} catch (NullPointerException e) {
Log.e("MusicUtils", "Failed to find file " + path);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void deleteFile(String path) {
new File(path).delete();
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void deleteSAF(Context context, String path, Uri safUri) {
Uri uri = null;
if (context == null) {
Log.e(TAG, "deleteSAF: context == null");
return;
}
if (isTreeUriSaved(context)) {
List<String> pathSegments = new ArrayList<>(Arrays.asList(path.split("/")));
Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.getSafSdCardUri());
uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments);
}
if (uri == null) {
uri = safUri;
}
if (uri == null) {
Log.e(TAG, "deleteSAF: Can't get SAF URI");
toast(context, context.getString(R.string.saf_error_uri));
return;
}
try {
DocumentsContract.deleteDocument(context.getContentResolver(), uri);
} catch (final Exception e) {
Log.e(TAG, "deleteSAF: Failed to delete a file descriptor provided by SAF", e);
toast(context, String.format(context.getString(R.string.saf_delete_failed), e.getLocalizedMessage()));
}
}
private static void toast(final Context context, final String message) {
if (context instanceof Activity) {
((Activity) context).runOnUiThread(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
}
}
}
}

View file

@ -15,58 +15,58 @@
package code.name.monkey.retromusic.util;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public class SwipeAndDragHelper extends ItemTouchHelper.Callback {
private ActionCompletionContract contract;
private ActionCompletionContract contract;
public SwipeAndDragHelper(@NonNull ActionCompletionContract contract) {
this.contract = contract;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
contract.onViewMoved(viewHolder.getLayoutPosition(), target.getLayoutPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public void onChildDraw(Canvas c,
RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
float dX,
float dY,
int actionState,
boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth());
viewHolder.itemView.setAlpha(alpha);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
public interface ActionCompletionContract {
void onViewMoved(int oldPosition, int newPosition);
public SwipeAndDragHelper(@NonNull ActionCompletionContract contract) {
this.contract = contract;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(
RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
contract.onViewMoved(viewHolder.getLayoutPosition(), target.getLayoutPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public void onChildDraw(
Canvas c,
RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
float dX,
float dY,
int actionState,
boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth());
viewHolder.itemView.setAlpha(alpha);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
public interface ActionCompletionContract {
void onViewMoved(int oldPosition, int newPosition);
}
}

View file

@ -14,69 +14,67 @@
package code.name.monkey.retromusic.util;
/**
* @author Hemanth S (h4h13).
*/
/** @author Hemanth S (h4h13). */
public class TempUtils {
// Enums
public static final int TEMPO_STROLL = 0;
public static final int TEMPO_WALK = 1;
public static final int TEMPO_LIGHT_JOG = 2;
public static final int TEMPO_JOG = 3;
public static final int TEMPO_RUN = 4;
public static final int TEMPO_SPRINT = 5;
public static final int TEMPO_UNKNOWN = 6;
// Enums
public static final int TEMPO_STROLL = 0;
public static final int TEMPO_WALK = 1;
public static final int TEMPO_LIGHT_JOG = 2;
public static final int TEMPO_JOG = 3;
public static final int TEMPO_RUN = 4;
public static final int TEMPO_SPRINT = 5;
public static final int TEMPO_UNKNOWN = 6;
// take BPM as an int
public static int getTempoFromBPM(int bpm) {
// take BPM as an int
public static int getTempoFromBPM(int bpm) {
// STROLL less than 60
if (bpm < 60) {
return TEMPO_STROLL;
}
// WALK between 60 and 70, or between 120 and 140
else if (bpm < 70 || bpm >= 120 && bpm < 140) {
return TEMPO_WALK;
}
// LIGHT_JOG between 70 and 80, or between 140 and 160
else if (bpm < 80 || bpm >= 140 && bpm < 160) {
return TEMPO_LIGHT_JOG;
}
// JOG between 80 and 90, or between 160 and 180
else if (bpm < 90 || bpm >= 160 && bpm < 180) {
return TEMPO_JOG;
}
// RUN between 90 and 100, or between 180 and 200
else if (bpm < 100 || bpm >= 180 && bpm < 200) {
return TEMPO_RUN;
}
// SPRINT between 100 and 120
else if (bpm < 120) {
return TEMPO_SPRINT;
}
// UNKNOWN
else {
return TEMPO_UNKNOWN;
}
// STROLL less than 60
if (bpm < 60) {
return TEMPO_STROLL;
}
// take BPM as a string
public static int getTempoFromBPM(String bpm) {
// cast to an int from string
try {
// convert the string to an int
return getTempoFromBPM(Integer.parseInt(bpm.trim()));
} catch (NumberFormatException nfe) {
//
return TEMPO_UNKNOWN;
}
// WALK between 60 and 70, or between 120 and 140
else if (bpm < 70 || bpm >= 120 && bpm < 140) {
return TEMPO_WALK;
}
// LIGHT_JOG between 70 and 80, or between 140 and 160
else if (bpm < 80 || bpm >= 140 && bpm < 160) {
return TEMPO_LIGHT_JOG;
}
// JOG between 80 and 90, or between 160 and 180
else if (bpm < 90 || bpm >= 160 && bpm < 180) {
return TEMPO_JOG;
}
// RUN between 90 and 100, or between 180 and 200
else if (bpm < 100 || bpm >= 180 && bpm < 200) {
return TEMPO_RUN;
}
// SPRINT between 100 and 120
else if (bpm < 120) {
return TEMPO_SPRINT;
}
// UNKNOWN
else {
return TEMPO_UNKNOWN;
}
}
// take BPM as a string
public static int getTempoFromBPM(String bpm) {
// cast to an int from string
try {
// convert the string to an int
return getTempoFromBPM(Integer.parseInt(bpm.trim()));
} catch (NumberFormatException nfe) {
//
return TEMPO_UNKNOWN;
}
}
}

View file

@ -30,118 +30,111 @@ import android.graphics.drawable.Drawable;
* @hide
*/
public class ImageUtils {
// Amount (max is 255) that two channels can differ before the color is no longer "gray".
private static final int TOLERANCE = 20;
// Alpha amount for which values below are considered transparent.
private static final int ALPHA_TOLERANCE = 50;
// Size of the smaller bitmap we're actually going to scan.
private static final int COMPACT_BITMAP_SIZE = 64; // pixels
private final Matrix mTempMatrix = new Matrix();
private int[] mTempBuffer;
private Bitmap mTempCompactBitmap;
private Canvas mTempCompactBitmapCanvas;
private Paint mTempCompactBitmapPaint;
// Amount (max is 255) that two channels can differ before the color is no longer "gray".
private static final int TOLERANCE = 20;
// Alpha amount for which values below are considered transparent.
private static final int ALPHA_TOLERANCE = 50;
// Size of the smaller bitmap we're actually going to scan.
private static final int COMPACT_BITMAP_SIZE = 64; // pixels
private final Matrix mTempMatrix = new Matrix();
private int[] mTempBuffer;
private Bitmap mTempCompactBitmap;
private Canvas mTempCompactBitmapCanvas;
private Paint mTempCompactBitmapPaint;
/**
* Classifies a color as grayscale or not. Grayscale here means "very close to a perfect
* gray"; if all three channels are approximately equal, this will return true.
* <p>
* Note that really transparent colors are always grayscale.
*/
public static boolean isGrayscale(int color) {
int alpha = 0xFF & (color >> 24);
if (alpha < ALPHA_TOLERANCE) {
return true;
}
int r = 0xFF & (color >> 16);
int g = 0xFF & (color >> 8);
int b = 0xFF & color;
return Math.abs(r - g) < TOLERANCE
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
/**
* Classifies a color as grayscale or not. Grayscale here means "very close to a perfect gray"; if
* all three channels are approximately equal, this will return true.
*
* <p>Note that really transparent colors are always grayscale.
*/
public static boolean isGrayscale(int color) {
int alpha = 0xFF & (color >> 24);
if (alpha < ALPHA_TOLERANCE) {
return true;
}
int r = 0xFF & (color >> 16);
int g = 0xFF & (color >> 8);
int b = 0xFF & color;
return Math.abs(r - g) < TOLERANCE
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
}
/**
* Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
*/
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight) {
if (drawable == null) {
return null;
}
int originalWidth = drawable.getIntrinsicWidth();
int originalHeight = drawable.getIntrinsicHeight();
if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) &&
(drawable instanceof BitmapDrawable)) {
return ((BitmapDrawable) drawable).getBitmap();
}
if (originalHeight <= 0 || originalWidth <= 0) {
return null;
}
// create a new bitmap, scaling down to fit the max dimensions of
// a large notification icon if necessary
float ratio = Math.min((float) maxWidth / (float) originalWidth,
(float) maxHeight / (float) originalHeight);
ratio = Math.min(1.0f, ratio);
int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
// and paint our app bitmap on it
Canvas canvas = new Canvas(result);
drawable.setBounds(0, 0, scaledWidth, scaledHeight);
drawable.draw(canvas);
return result;
/** Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. */
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, int maxHeight) {
if (drawable == null) {
return null;
}
/**
* Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect
* gray".
* <p>
* Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than
* COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements
* will survive the squeezing process, contaminating the result with color.
*/
public boolean isGrayscale(Bitmap bitmap) {
int height = bitmap.getHeight();
int width = bitmap.getWidth();
// shrink to a more manageable (yet hopefully no more or less colorful) size
if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) {
if (mTempCompactBitmap == null) {
mTempCompactBitmap = Bitmap.createBitmap(
COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Config.ARGB_8888
);
mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap);
mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTempCompactBitmapPaint.setFilterBitmap(true);
}
mTempMatrix.reset();
mTempMatrix.setScale(
(float) COMPACT_BITMAP_SIZE / width,
(float) COMPACT_BITMAP_SIZE / height,
0, 0);
mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase
mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint);
bitmap = mTempCompactBitmap;
width = height = COMPACT_BITMAP_SIZE;
}
final int size = height * width;
ensureBufferSize(size);
bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height);
for (int i = 0; i < size; i++) {
if (!isGrayscale(mTempBuffer[i])) {
return false;
}
}
return true;
int originalWidth = drawable.getIntrinsicWidth();
int originalHeight = drawable.getIntrinsicHeight();
if ((originalWidth <= maxWidth)
&& (originalHeight <= maxHeight)
&& (drawable instanceof BitmapDrawable)) {
return ((BitmapDrawable) drawable).getBitmap();
}
/**
* Makes sure that {@code mTempBuffer} has at least length {@code size}.
*/
private void ensureBufferSize(int size) {
if (mTempBuffer == null || mTempBuffer.length < size) {
mTempBuffer = new int[size];
}
if (originalHeight <= 0 || originalWidth <= 0) {
return null;
}
}
// create a new bitmap, scaling down to fit the max dimensions of
// a large notification icon if necessary
float ratio =
Math.min(
(float) maxWidth / (float) originalWidth, (float) maxHeight / (float) originalHeight);
ratio = Math.min(1.0f, ratio);
int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
// and paint our app bitmap on it
Canvas canvas = new Canvas(result);
drawable.setBounds(0, 0, scaledWidth, scaledHeight);
drawable.draw(canvas);
return result;
}
/**
* Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect gray".
*
* <p>Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than
* COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements will
* survive the squeezing process, contaminating the result with color.
*/
public boolean isGrayscale(Bitmap bitmap) {
int height = bitmap.getHeight();
int width = bitmap.getWidth();
// shrink to a more manageable (yet hopefully no more or less colorful) size
if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) {
if (mTempCompactBitmap == null) {
mTempCompactBitmap =
Bitmap.createBitmap(COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Config.ARGB_8888);
mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap);
mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTempCompactBitmapPaint.setFilterBitmap(true);
}
mTempMatrix.reset();
mTempMatrix.setScale(
(float) COMPACT_BITMAP_SIZE / width, (float) COMPACT_BITMAP_SIZE / height, 0, 0);
mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase
mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint);
bitmap = mTempCompactBitmap;
width = height = COMPACT_BITMAP_SIZE;
}
final int size = height * width;
ensureBufferSize(size);
bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height);
for (int i = 0; i < size; i++) {
if (!isGrayscale(mTempBuffer[i])) {
return false;
}
}
return true;
}
/** Makes sure that {@code mTempBuffer} has at least length {@code size}. */
private void ensureBufferSize(int size) {
if (mTempBuffer == null || mTempBuffer.length < size) {
mTempBuffer = new int[size];
}
}
}

View file

@ -16,6 +16,8 @@
package code.name.monkey.retromusic.util.color;
import static androidx.core.graphics.ColorUtils.RGBToXYZ;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@ -23,485 +25,472 @@ import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.palette.graphics.Palette;
import java.util.List;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.retromusic.R;
import java.util.List;
import static androidx.core.graphics.ColorUtils.RGBToXYZ;
/**
* A class the processes media notifications and extracts the right text and background colors.
*/
/** A class the processes media notifications and extracts the right text and background colors. */
public class MediaNotificationProcessor {
/**
* The fraction below which we select the vibrant instead of the light/dark vibrant color
*/
private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
/** The fraction below which we select the vibrant instead of the light/dark vibrant color */
private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
/**
* Minimum saturation that a muted color must have if there exists if deciding between two
* colors
*/
private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
/**
* Minimum saturation that a muted color must have if there exists if deciding between two colors
*/
private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
/**
* Minimum fraction that any color must have to be picked up as a text color
*/
private static final double MINIMUM_IMAGE_FRACTION = 0.002;
/** Minimum fraction that any color must have to be picked up as a text color */
private static final double MINIMUM_IMAGE_FRACTION = 0.002;
/**
* The population fraction to select the dominant color as the text color over a the colored
* ones.
*/
private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
/**
* The population fraction to select the dominant color as the text color over a the colored ones.
*/
private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
/**
* The population fraction to select a white or black color as the background over a color.
*/
private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
private static final ThreadLocal<double[]> TEMP_ARRAY = new ThreadLocal<>();
/**
* The lightness difference that has to be added to the primary text color to obtain the
* secondary text color when the background is light.
*/
private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
/**
* The lightness difference that has to be added to the primary text color to obtain the
* secondary text color when the background is dark.
* A bit less then the above value, since it looks better on dark backgrounds.
*/
private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
private static final String TAG = "ColorPicking";
private float[] mFilteredBackgroundHsl = null;
private Palette.Filter mBlackWhiteFilter = new Palette.Filter() {
/** The population fraction to select a white or black color as the background over a color. */
private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
private static final ThreadLocal<double[]> TEMP_ARRAY = new ThreadLocal<>();
/**
* The lightness difference that has to be added to the primary text color to obtain the secondary
* text color when the background is light.
*/
private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
/**
* The lightness difference that has to be added to the primary text color to obtain the secondary
* text color when the background is dark. A bit less then the above value, since it looks better
* on dark backgrounds.
*/
private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
private static final String TAG = "ColorPicking";
private float[] mFilteredBackgroundHsl = null;
private Palette.Filter mBlackWhiteFilter =
new Palette.Filter() {
@Override
public boolean isAllowed(int rgb, @NonNull float[] hsl) {
return !isWhiteOrBlack(hsl);
return !isWhiteOrBlack(hsl);
}
};
private boolean mIsLowPriority;
private int backgroundColor;
private int secondaryTextColor;
private int primaryTextColor;
private int actionBarColor;
private Drawable drawable;
private Context context;
};
private boolean mIsLowPriority;
private int backgroundColor;
private int secondaryTextColor;
private int primaryTextColor;
private int actionBarColor;
private Drawable drawable;
private Context context;
public MediaNotificationProcessor(Context context, Drawable drawable) {
this.context = context;
this.drawable = drawable;
getMediaPalette();
public MediaNotificationProcessor(Context context, Drawable drawable) {
this.context = context;
this.drawable = drawable;
getMediaPalette();
}
public MediaNotificationProcessor(Context context, Bitmap bitmap) {
this.context = context;
this.drawable = new BitmapDrawable(context.getResources(), bitmap);
getMediaPalette();
}
public MediaNotificationProcessor(Context context) {
this.context = context;
}
private static boolean isColorLight(int backgroundColor) {
return calculateLuminance(backgroundColor) > 0.5f;
}
/**
* Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
*
* <p>Defined as the Y component in the XYZ representation of {@code color}.
*/
@FloatRange(from = 0.0, to = 1.0)
private static double calculateLuminance(@ColorInt int color) {
final double[] result = getTempDouble3Array();
colorToXYZ(color, result);
// Luminance is the Y component
return result[1] / 100;
}
private static double[] getTempDouble3Array() {
double[] result = TEMP_ARRAY.get();
if (result == null) {
result = new double[3];
TEMP_ARRAY.set(result);
}
return result;
}
private static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
}
public MediaNotificationProcessor(Context context, Bitmap bitmap) {
this.context = context;
this.drawable = new BitmapDrawable(context.getResources(), bitmap);
getMediaPalette();
}
public MediaNotificationProcessor(Context context) {
this.context = context;
}
private static boolean isColorLight(int backgroundColor) {
return calculateLuminance(backgroundColor) > 0.5f;
}
/**
* Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
* <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
*/
@FloatRange(from = 0.0, to = 1.0)
private static double calculateLuminance(@ColorInt int color) {
final double[] result = getTempDouble3Array();
colorToXYZ(color, result);
// Luminance is the Y component
return result[1] / 100;
}
private static double[] getTempDouble3Array() {
double[] result = TEMP_ARRAY.get();
if (result == null) {
result = new double[3];
TEMP_ARRAY.set(result);
}
return result;
}
private static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
}
public void getPaletteAsync(final OnPaletteLoadedListener onPaletteLoadedListener, Drawable drawable) {
this.drawable = drawable;
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
public void getPaletteAsync(
final OnPaletteLoadedListener onPaletteLoadedListener, Drawable drawable) {
this.drawable = drawable;
final Handler handler = new Handler();
new Thread(
new Runnable() {
@Override
public void run() {
getMediaPalette();
handler.post(new Runnable() {
@Override
public void run() {
handler.post(
new Runnable() {
@Override
public void run() {
onPaletteLoadedListener.onPaletteLoaded(MediaNotificationProcessor.this);
}
});
}
}).start();
}
public void getPaletteAsync(OnPaletteLoadedListener onPaletteLoadedListener, Bitmap bitmap) {
this.drawable = new BitmapDrawable(context.getResources(), bitmap);
getPaletteAsync(onPaletteLoadedListener, this.drawable);
}
/**
* Processes a drawable and calculates the appropriate colors that should
* be used.
*/
private void getMediaPalette() {
Bitmap bitmap;
if (drawable != null) {
// We're transforming the builder, let's make sure all baked in RemoteViews are
// rebuilt!
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
int area = width * height;
if (area > RESIZE_BITMAP_AREA) {
double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
width = (int) (factor * width);
height = (int) (factor * height);
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
// for the background we only take the left side of the image to ensure
// a smooth transition
Palette.Builder paletteBuilder = Palette.from(bitmap)
.setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight())
.clearFilters() // we want all colors, red / white / black ones too!
.resizeBitmapArea(RESIZE_BITMAP_AREA);
Palette palette = paletteBuilder.generate();
backgroundColor = findBackgroundColorAndFilter(drawable);
// we want most of the full region again, slightly shifted to the right
float textColorStartWidthFraction = 0.4f;
paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
bitmap.getWidth(),
bitmap.getHeight());
if (mFilteredBackgroundHsl != null) {
paletteBuilder.addFilter(new Palette.Filter() {
@Override
public boolean isAllowed(int rgb, @NonNull float[] hsl) {
// at least 10 degrees hue difference
float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]);
return diff > 10 && diff < 350;
}
}
});
}
paletteBuilder.addFilter(mBlackWhiteFilter);
palette = paletteBuilder.generate();
int foregroundColor = selectForegroundColor(backgroundColor, palette);
ensureColors(backgroundColor, foregroundColor);
}
}
}
})
.start();
}
}
public void getPaletteAsync(OnPaletteLoadedListener onPaletteLoadedListener, Bitmap bitmap) {
this.drawable = new BitmapDrawable(context.getResources(), bitmap);
getPaletteAsync(onPaletteLoadedListener, this.drawable);
}
private int selectForegroundColor(int backgroundColor, Palette palette) {
if (isColorLight(backgroundColor)) {
return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
palette.getVibrantSwatch(),
palette.getDarkMutedSwatch(),
palette.getMutedSwatch(),
palette.getDominantSwatch(),
Color.BLACK);
} else {
return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(),
palette.getVibrantSwatch(),
palette.getLightMutedSwatch(),
palette.getMutedSwatch(),
palette.getDominantSwatch(),
Color.WHITE);
}
}
public boolean isLight() {
return isColorLight(backgroundColor);
}
private int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
Palette.Swatch dominantSwatch, int fallbackColor) {
Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
if (coloredCandidate == null) {
coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
}
if (coloredCandidate != null) {
if (dominantSwatch == coloredCandidate) {
return coloredCandidate.getRgb();
} else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
< POPULATION_FRACTION_FOR_DOMINANT
&& dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
return dominantSwatch.getRgb();
} else {
return coloredCandidate.getRgb();
}
} else if (hasEnoughPopulation(dominantSwatch)) {
return dominantSwatch.getRgb();
} else {
return fallbackColor;
}
}
private Palette.Swatch selectMutedCandidate(Palette.Swatch first,
Palette.Swatch second) {
boolean firstValid = hasEnoughPopulation(first);
boolean secondValid = hasEnoughPopulation(second);
if (firstValid && secondValid) {
float firstSaturation = first.getHsl()[1];
float secondSaturation = second.getHsl()[1];
float populationFraction = first.getPopulation() / (float) second.getPopulation();
if (firstSaturation * populationFraction > secondSaturation) {
return first;
} else {
return second;
}
} else if (firstValid) {
return first;
} else if (secondValid) {
return second;
}
return null;
}
private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) {
boolean firstValid = hasEnoughPopulation(first);
boolean secondValid = hasEnoughPopulation(second);
if (firstValid && secondValid) {
int firstPopulation = first.getPopulation();
int secondPopulation = second.getPopulation();
if (firstPopulation / (float) secondPopulation
< POPULATION_FRACTION_FOR_MORE_VIBRANT) {
return second;
} else {
return first;
}
} else if (firstValid) {
return first;
} else if (secondValid) {
return second;
}
return null;
}
private boolean hasEnoughPopulation(Palette.Swatch swatch) {
// We want a fraction that is at least 1% of the image
return swatch != null
&& (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
}
public int findBackgroundColorAndFilter(Drawable drawable) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
int area = width * height;
/** Processes a drawable and calculates the appropriate colors that should be used. */
private void getMediaPalette() {
Bitmap bitmap;
if (drawable != null) {
// We're transforming the builder, let's make sure all baked in RemoteViews are
// rebuilt!
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
int area = width * height;
if (area > RESIZE_BITMAP_AREA) {
double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
width = (int) (factor * width);
height = (int) (factor * height);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
// for the background we only take the left side of the image to ensure
// a smooth transition
Palette.Builder paletteBuilder = Palette.from(bitmap)
Palette.Builder paletteBuilder =
Palette.from(bitmap)
.setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight())
.clearFilters() // we want all colors, red / white / black ones too!
.resizeBitmapArea(RESIZE_BITMAP_AREA);
Palette palette = paletteBuilder.generate();
// by default we use the dominant palette
Palette.Swatch dominantSwatch = palette.getDominantSwatch();
if (dominantSwatch == null) {
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return Color.WHITE;
backgroundColor = findBackgroundColorAndFilter(drawable);
// we want most of the full region again, slightly shifted to the right
float textColorStartWidthFraction = 0.4f;
paletteBuilder.setRegion(
(int) (bitmap.getWidth() * textColorStartWidthFraction),
0,
bitmap.getWidth(),
bitmap.getHeight());
if (mFilteredBackgroundHsl != null) {
paletteBuilder.addFilter(
new Palette.Filter() {
@Override
public boolean isAllowed(int rgb, @NonNull float[] hsl) {
// at least 10 degrees hue difference
float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]);
return diff > 10 && diff < 350;
}
});
}
paletteBuilder.addFilter(mBlackWhiteFilter);
palette = paletteBuilder.generate();
int foregroundColor = selectForegroundColor(backgroundColor, palette);
ensureColors(backgroundColor, foregroundColor);
}
}
}
if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
mFilteredBackgroundHsl = dominantSwatch.getHsl();
return dominantSwatch.getRgb();
}
// Oh well, we selected black or white. Lets look at the second color!
List<Palette.Swatch> swatches = palette.getSwatches();
float highestNonWhitePopulation = -1;
Palette.Swatch second = null;
for (Palette.Swatch swatch : swatches) {
if (swatch != dominantSwatch
&& swatch.getPopulation() > highestNonWhitePopulation
&& !isWhiteOrBlack(swatch.getHsl())) {
second = swatch;
highestNonWhitePopulation = swatch.getPopulation();
}
}
if (second == null) {
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return dominantSwatch.getRgb();
}
if (dominantSwatch.getPopulation() / highestNonWhitePopulation
> POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
// The dominant swatch is very dominant, lets take it!
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return dominantSwatch.getRgb();
private int selectForegroundColor(int backgroundColor, Palette palette) {
if (isColorLight(backgroundColor)) {
return selectForegroundColorForSwatches(
palette.getDarkVibrantSwatch(),
palette.getVibrantSwatch(),
palette.getDarkMutedSwatch(),
palette.getMutedSwatch(),
palette.getDominantSwatch(),
Color.BLACK);
} else {
return selectForegroundColorForSwatches(
palette.getLightVibrantSwatch(),
palette.getVibrantSwatch(),
palette.getLightMutedSwatch(),
palette.getMutedSwatch(),
palette.getDominantSwatch(),
Color.WHITE);
}
}
public boolean isLight() {
return isColorLight(backgroundColor);
}
private int selectForegroundColorForSwatches(
Palette.Swatch moreVibrant,
Palette.Swatch vibrant,
Palette.Swatch moreMutedSwatch,
Palette.Swatch mutedSwatch,
Palette.Swatch dominantSwatch,
int fallbackColor) {
Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
if (coloredCandidate == null) {
coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
}
if (coloredCandidate != null) {
if (dominantSwatch == coloredCandidate) {
return coloredCandidate.getRgb();
} else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
< POPULATION_FRACTION_FOR_DOMINANT
&& dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
return dominantSwatch.getRgb();
} else {
return coloredCandidate.getRgb();
}
} else if (hasEnoughPopulation(dominantSwatch)) {
return dominantSwatch.getRgb();
} else {
return fallbackColor;
}
}
private Palette.Swatch selectMutedCandidate(Palette.Swatch first, Palette.Swatch second) {
boolean firstValid = hasEnoughPopulation(first);
boolean secondValid = hasEnoughPopulation(second);
if (firstValid && secondValid) {
float firstSaturation = first.getHsl()[1];
float secondSaturation = second.getHsl()[1];
float populationFraction = first.getPopulation() / (float) second.getPopulation();
if (firstSaturation * populationFraction > secondSaturation) {
return first;
} else {
return second;
}
} else if (firstValid) {
return first;
} else if (secondValid) {
return second;
}
return null;
}
private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) {
boolean firstValid = hasEnoughPopulation(first);
boolean secondValid = hasEnoughPopulation(second);
if (firstValid && secondValid) {
int firstPopulation = first.getPopulation();
int secondPopulation = second.getPopulation();
if (firstPopulation / (float) secondPopulation < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
return second;
} else {
return first;
}
} else if (firstValid) {
return first;
} else if (secondValid) {
return second;
}
return null;
}
private boolean hasEnoughPopulation(Palette.Swatch swatch) {
// We want a fraction that is at least 1% of the image
return swatch != null
&& (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
}
public int findBackgroundColorAndFilter(Drawable drawable) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
int area = width * height;
double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
width = (int) (factor * width);
height = (int) (factor * height);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
// for the background we only take the left side of the image to ensure
// a smooth transition
Palette.Builder paletteBuilder =
Palette.from(bitmap)
.setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight())
.clearFilters() // we want all colors, red / white / black ones too!
.resizeBitmapArea(RESIZE_BITMAP_AREA);
Palette palette = paletteBuilder.generate();
// by default we use the dominant palette
Palette.Swatch dominantSwatch = palette.getDominantSwatch();
if (dominantSwatch == null) {
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return Color.WHITE;
}
if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
mFilteredBackgroundHsl = dominantSwatch.getHsl();
return dominantSwatch.getRgb();
}
// Oh well, we selected black or white. Lets look at the second color!
List<Palette.Swatch> swatches = palette.getSwatches();
float highestNonWhitePopulation = -1;
Palette.Swatch second = null;
for (Palette.Swatch swatch : swatches) {
if (swatch != dominantSwatch
&& swatch.getPopulation() > highestNonWhitePopulation
&& !isWhiteOrBlack(swatch.getHsl())) {
second = swatch;
highestNonWhitePopulation = swatch.getPopulation();
}
}
if (second == null) {
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return dominantSwatch.getRgb();
}
if (dominantSwatch.getPopulation() / highestNonWhitePopulation
> POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
// The dominant swatch is very dominant, lets take it!
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return dominantSwatch.getRgb();
} else {
mFilteredBackgroundHsl = second.getHsl();
return second.getRgb();
}
}
private boolean isWhiteOrBlack(float[] hsl) {
return isBlack(hsl) || isWhite(hsl);
}
/** @return true if the color represents a color which is close to black. */
private boolean isBlack(float[] hslColor) {
return hslColor[2] <= BLACK_MAX_LIGHTNESS;
}
/** @return true if the color represents a color which is close to white. */
private boolean isWhite(float[] hslColor) {
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
}
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
}
private void ensureColors(int backgroundColor, int mForegroundColor) {
{
double backLum = NotificationColorUtil.calculateLuminance(backgroundColor);
double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor);
double contrast = NotificationColorUtil.calculateContrast(mForegroundColor, backgroundColor);
// We only respect the given colors if worst case Black or White still has
// contrast
boolean backgroundLight =
backLum > textLum
&& NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.BLACK)
|| backLum <= textLum
&& !NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.WHITE);
if (contrast < 4.5f) {
if (backgroundLight) {
secondaryTextColor =
NotificationColorUtil.findContrastColor(
mForegroundColor, backgroundColor, true /* findFG */, 4.5f);
primaryTextColor =
NotificationColorUtil.changeColorLightness(
secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
} else {
mFilteredBackgroundHsl = second.getHsl();
return second.getRgb();
secondaryTextColor =
NotificationColorUtil.findContrastColorAgainstDark(
mForegroundColor, backgroundColor, true /* findFG */, 4.5f);
primaryTextColor =
NotificationColorUtil.changeColorLightness(
secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
}
private boolean isWhiteOrBlack(float[] hsl) {
return isBlack(hsl) || isWhite(hsl);
}
/**
* @return true if the color represents a color which is close to black.
*/
private boolean isBlack(float[] hslColor) {
return hslColor[2] <= BLACK_MAX_LIGHTNESS;
}
/**
* @return true if the color represents a color which is close to white.
*/
private boolean isWhite(float[] hslColor) {
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
}
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
}
private void ensureColors(int backgroundColor, int mForegroundColor) {
{
double backLum = NotificationColorUtil.calculateLuminance(backgroundColor);
double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor);
double contrast = NotificationColorUtil.calculateContrast(mForegroundColor,
backgroundColor);
// We only respect the given colors if worst case Black or White still has
// contrast
boolean backgroundLight = backLum > textLum
&& NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.BLACK)
|| backLum <= textLum
&& !NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.WHITE);
if (contrast < 4.5f) {
if (backgroundLight) {
secondaryTextColor = NotificationColorUtil.findContrastColor(
mForegroundColor,
backgroundColor,
true /* findFG */,
4.5f);
primaryTextColor = NotificationColorUtil.changeColorLightness(
secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
} else {
secondaryTextColor =
NotificationColorUtil.findContrastColorAgainstDark(
mForegroundColor,
backgroundColor,
true /* findFG */,
4.5f);
primaryTextColor = NotificationColorUtil.changeColorLightness(
secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
} else {
primaryTextColor = mForegroundColor;
secondaryTextColor = NotificationColorUtil.changeColorLightness(
primaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: LIGHTNESS_TEXT_DIFFERENCE_DARK);
if (NotificationColorUtil.calculateContrast(secondaryTextColor,
backgroundColor) < 4.5f) {
// oh well the secondary is not good enough
if (backgroundLight) {
secondaryTextColor = NotificationColorUtil.findContrastColor(
secondaryTextColor,
backgroundColor,
true /* findFG */,
4.5f);
} else {
secondaryTextColor
= NotificationColorUtil.findContrastColorAgainstDark(
secondaryTextColor,
backgroundColor,
true /* findFG */,
4.5f);
}
primaryTextColor = NotificationColorUtil.changeColorLightness(
secondaryTextColor, backgroundLight
? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
}
} else {
primaryTextColor = mForegroundColor;
secondaryTextColor =
NotificationColorUtil.changeColorLightness(
primaryTextColor,
backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT : LIGHTNESS_TEXT_DIFFERENCE_DARK);
if (NotificationColorUtil.calculateContrast(secondaryTextColor, backgroundColor) < 4.5f) {
// oh well the secondary is not good enough
if (backgroundLight) {
secondaryTextColor =
NotificationColorUtil.findContrastColor(
secondaryTextColor, backgroundColor, true /* findFG */, 4.5f);
} else {
secondaryTextColor =
NotificationColorUtil.findContrastColorAgainstDark(
secondaryTextColor, backgroundColor, true /* findFG */, 4.5f);
}
primaryTextColor =
NotificationColorUtil.changeColorLightness(
secondaryTextColor,
backgroundLight
? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
actionBarColor = NotificationColorUtil.resolveActionBarColor(context,
backgroundColor);
}
}
actionBarColor = NotificationColorUtil.resolveActionBarColor(context, backgroundColor);
}
public int getPrimaryTextColor() {
public int getPrimaryTextColor() {
return primaryTextColor;
}
public int getSecondaryTextColor() {
return secondaryTextColor;
}
public int getActionBarColor() {
return actionBarColor;
}
public int getBackgroundColor() {
return backgroundColor;
}
boolean isWhiteColor(int color) {
return calculateLuminance(color) > 0.6f;
}
public int getMightyColor() {
boolean isDarkBg =
ColorUtil.INSTANCE.isColorLight(
ATHUtil.INSTANCE.resolveColor(context, R.attr.colorSurface));
if (isDarkBg) {
if (isColorLight(backgroundColor)) {
return primaryTextColor;
}
public int getSecondaryTextColor() {
return secondaryTextColor;
}
public int getActionBarColor() {
return actionBarColor;
}
public int getBackgroundColor() {
} else {
return backgroundColor;
}
} else {
if (isColorLight(backgroundColor)) {
return backgroundColor;
} else {
return primaryTextColor;
}
}
}
boolean isWhiteColor(int color) {
return calculateLuminance(color) > 0.6f;
}
public int getMightyColor() {
boolean isDarkBg = ColorUtil.INSTANCE.isColorLight(ATHUtil.INSTANCE.resolveColor(context, R.attr.colorSurface));
if (isDarkBg) {
if (isColorLight(backgroundColor)) {
return primaryTextColor;
} else {
return backgroundColor;
}
} else {
if (isColorLight(backgroundColor)) {
return backgroundColor;
} else {
return primaryTextColor;
}
}
}
public interface OnPaletteLoadedListener {
void onPaletteLoaded(MediaNotificationProcessor mediaNotificationProcessor);
}
}
public interface OnPaletteLoadedListener {
void onPaletteLoaded(MediaNotificationProcessor mediaNotificationProcessor);
}
}