Fix classic notification size

main
h4h13 2019-10-29 20:19:28 +05:30
parent b490311cbf
commit 8ef8a3955f
19 changed files with 403 additions and 1484 deletions

View File

@ -12,10 +12,10 @@ import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import code.name.monkey.retromusic.views.DrawableGradient import code.name.monkey.retromusic.views.DrawableGradient
@ -100,16 +100,6 @@ class PlayerFragment : AbsPlayerFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setUpSubFragments() setUpSubFragments()
setUpPlayerToolbar() setUpPlayerToolbar()
//val display = activity?.windowManager?.defaultDisplay
//val outMetrics = DisplayMetrics()
//display?.getMetrics(outMetrics)
//val density = resources.displayMetrics.density
//val dpWidth = outMetrics.widthPixels / density
//playerAlbumCoverContainer?.layoutParams?.height = RetroUtil.convertDpToPixel((dpWidth - getCutOff()), context!!).toInt()
} }

View File

@ -77,7 +77,6 @@ import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl2
import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo; import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo;
import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.service.playback.Playback;
import code.name.monkey.retromusic.util.MusicUtil; import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PackageValidator;
import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroUtil; import code.name.monkey.retromusic.util.RetroUtil;
@ -275,7 +274,6 @@ public class MusicService extends Service implements
} }
} }
}; };
private PackageValidator mPackageValidator;
private static String getTrackUri(@NonNull Song song) { private static String getTrackUri(@NonNull Song song) {
return MusicUtil.getSongFileUri(song.getId()).toString(); return MusicUtil.getSongFileUri(song.getId()).toString();
@ -341,9 +339,6 @@ public class MusicService extends Service implements
restoreState(); restoreState();
mPackageValidator = new PackageValidator(this, R.xml.allowed_media_browser_callers);
sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED"));
registerHeadsetEvents(); registerHeadsetEvents();

View File

@ -1,30 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util
import android.content.Context
import androidx.annotation.AttrRes
object AttrsUtil {
@JvmOverloads
fun resolveColor(context: Context, @AttrRes attr: Int, fallback: Int = 0): Int {
val a = context.theme.obtainStyledAttributes(intArrayOf(attr))
try {
return a.getColor(0, fallback)
} finally {
a.recycle()
}
}
}

View File

@ -16,9 +16,7 @@ package code.name.monkey.retromusic.util;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.TypedValue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -33,38 +31,9 @@ public class DensityUtil {
return displayMetrics.heightPixels; return displayMetrics.heightPixels;
} }
public static int getScreenWidth(@NonNull Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels;
}
public static int dip2px(@NonNull Context context, float dpVale) { public static int dip2px(@NonNull Context context, float dpVale) {
final float scale = context.getResources().getDisplayMetrics().density; final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpVale * scale + 0.5f); return (int) (dpVale * scale + 0.5f);
} }
public static int getStatusBarHeight(@NonNull Context context) {
Resources resources = context.getResources();
int resourcesId = resources.getIdentifier("status_bar_height", "dimen", "android");
int height = resources.getDimensionPixelSize(resourcesId);
return height;
}
/**
* Converts sp to px
*
* @param context Context
* @param sp the value in sp
* @return int
*/
public static int dip2sp(@NonNull Context context, float sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
}
public static int px2dip(@NonNull Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
} }

View File

@ -47,7 +47,6 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.loaders.PlaylistLoader; import code.name.monkey.retromusic.loaders.PlaylistLoader;
import code.name.monkey.retromusic.loaders.SongLoader; import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.model.Artist; import code.name.monkey.retromusic.model.Artist;
import code.name.monkey.retromusic.model.Genre;
import code.name.monkey.retromusic.model.Playlist; import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics; import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
@ -72,17 +71,6 @@ public class MusicUtil {
@NonNull @NonNull
public static Intent createShareSongFileIntent(@NonNull final Song song, @NonNull Context context) { public static Intent createShareSongFileIntent(@NonNull final Song song, @NonNull Context context) {
/*Uri file = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", new File(song.getData()));
try {
return new Intent().setAction(Intent.ACTION_SEND).putExtra(Intent.EXTRA_STREAM, file)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setType("audio/*");
} catch (IllegalArgumentException e) {
e.printStackTrace();
Toast.makeText(context, "Could not share this file, I'm aware of the issue.", Toast.LENGTH_SHORT).show();
return new Intent();
}*/
try { try {
return new Intent() return new Intent()
.setAction(Intent.ACTION_SEND) .setAction(Intent.ACTION_SEND)
@ -123,36 +111,6 @@ public class MusicUtil {
return albumCount + " " + albumString + " • " + songCount + " " + songString; return albumCount + " " + albumString + " • " + songCount + " " + songString;
} }
@NonNull
public static String getArtistInfoStringSmall(@NonNull final Context context,
@NonNull final Artist artist) {
int songCount = artist.getSongCount();
String songString = songCount == 1 ? context.getResources().getString(R.string.song)
: context.getResources().getString(R.string.songs);
return songCount + " " + songString;
}
/*@NonNull
public static String getPlaylistInfoString(@NonNull final Context context,
@NonNull List<Song> songs) {
final int songCount = songs.size();
final String songString = songCount == 1 ? context.getResources().getString(R.string.song)
: context.getResources().getString(R.string.songs);
long duration = 0;
for (int i = 0; i < songs.size(); i++) {
duration += songs.get(i).getDuration();
}
return songCount + " " + songString + " • " + MusicUtil.getReadableDurationString(duration);
}*/
@NonNull
public static String getGenreInfoString(@NonNull final Context context, @NonNull final Genre genre) {
int songCount = genre.getSongCount();
return MusicUtil.getSongCountString(context, songCount);
}
@NonNull @NonNull
public static String getPlaylistInfoString(@NonNull final Context context, @NonNull List<Song> songs) { public static String getPlaylistInfoString(@NonNull final Context context, @NonNull List<Song> songs) {
final long duration = getTotalDuration(context, songs); final long duration = getTotalDuration(context, songs);
@ -185,31 +143,6 @@ public class MusicUtil {
return string1 + " • " + string2; return string1 + " • " + string2;
} }
/**
* Build a concatenated string from the provided arguments
* The intended purpose is to show extra annotations
* to a music library item.
* Ex: for a given album --> buildInfoString(album.artist, album.songCount)
*/
@NonNull
public static String buildInfoString(@Nullable final String string1, @Nullable final String string2, @NonNull final String string3) {
// Skip empty strings
if (TextUtils.isEmpty(string1)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string2) ? "" : string2;
}
if (TextUtils.isEmpty(string2)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string1;
}
if (TextUtils.isEmpty(string3)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string3;
}
return string1 + " • " + string2 + " • " + string3;
}
public static String getReadableDurationString(long songDurationMillis) { public static String getReadableDurationString(long songDurationMillis) {
long minutes = (songDurationMillis / 1000) / 60; long minutes = (songDurationMillis / 1000) / 60;
long seconds = (songDurationMillis / 1000) % 60; long seconds = (songDurationMillis / 1000) % 60;

View File

@ -1,347 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util
import android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE
import android.Manifest.permission.MEDIA_CONTENT_CONTROL
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
import android.content.pm.PackageManager
import android.content.res.XmlResourceParser
import android.os.Process
import android.support.v4.media.session.MediaSessionCompat
import android.util.Base64
import android.util.Log
import androidx.annotation.XmlRes
import androidx.media.MediaBrowserServiceCompat
import code.name.monkey.retromusic.BuildConfig
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
/**
* Validates that the calling package is authorized to browse a [MediaBrowserServiceCompat].
*
* The list of allowed signing certificates and their corresponding package names is defined in
* res/xml/allowed_media_browser_callers.xml.
*
* If you want to add a new caller to allowed_media_browser_callers.xml and you don't know
* its signature, this class will print to logcat (INFO level) a message with the proper
* xml tags to add to allow the caller.
*
* For more information, see res/xml/allowed_media_browser_callers.xml.
*/
class PackageValidator(
context: Context,
@XmlRes xmlResId: Int
) {
private val context: Context
private val packageManager: PackageManager
private val certificateWhitelist: Map<String, KnownCallerInfo>
private val platformSignature: String
private val callerChecked = mutableMapOf<String, Pair<Int, Boolean>>()
init {
val parser = context.resources.getXml(xmlResId)
this.context = context.applicationContext
this.packageManager = this.context.packageManager
certificateWhitelist = buildCertificateWhitelist(parser)
platformSignature = getSystemSignature()
}
/**
* Checks whether the caller attempting to connect to a [MediaBrowserServiceCompat] is known.
* See [MusicService.onGetRoot] for where this is utilized.
*
* @param callingPackage The package name of the caller.
* @param callingUid The user id of the caller.
* @return `true` if the caller is known, `false` otherwise.
*/
fun isKnownCaller(callingPackage: String, callingUid: Int): Boolean {
// If the caller has already been checked, return the previous result here.
val (checkedUid, checkResult) = callerChecked[callingPackage] ?: Pair(0, false)
if (checkedUid == callingUid) {
return checkResult
}
/**
* Because some of these checks can be slow, we save the results in [callerChecked] after
* this code is run.
*
* In particular, there's little reason to recompute the calling package's certificate
* signature (SHA-256) each call.
*
* This is safe to do as we know the UID matches the package's UID (from the check above),
* and app UIDs are set at install time. Additionally, a package name + UID is guaranteed to
* be constant until a reboot. (After a reboot then a previously assigned UID could be
* reassigned.)
*/
// Build the caller info for the rest of the checks here.
val callerPackageInfo = buildCallerInfo(callingPackage)
?: throw IllegalStateException("Caller wasn't found in the system?")
// Verify that things aren't ... broken. (This test should always pass.)
if (callerPackageInfo.uid != callingUid) {
throw IllegalStateException("Caller's package UID doesn't match caller's actual UID?")
}
val callerSignature = callerPackageInfo.signature
val isPackageInWhitelist = certificateWhitelist[callingPackage]?.signatures?.first {
it.signature == callerSignature
} != null
val isCallerKnown = when {
// If it's our own app making the call, allow it.
callingUid == Process.myUid() -> true
// If it's one of the apps on the whitelist, allow it.
isPackageInWhitelist -> true
// If the system is making the call, allow it.
callingUid == Process.SYSTEM_UID -> true
// If the app was signed by the same certificate as the platform itself, also allow it.
callerSignature == platformSignature -> true
/**
* [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and
* while it isn't required to allow these apps to connect to a
* [MediaBrowserServiceCompat], allowing this ensures optimal compatability with apps
* such as Android TV and the Google Assistant.
*/
callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true
/**
* This last permission can be specifically granted to apps, and, in addition to
* allowing them to retrieve notifications, it also allows them to connect to an
* active [MediaSessionCompat].
* As with the above, it's not required to allow apps holding this permission to
* connect to your [MediaBrowserServiceCompat], but it does allow easy comparability
* with apps such as Wear OS.
*/
callerPackageInfo.permissions.contains(BIND_NOTIFICATION_LISTENER_SERVICE) -> true
// If none of the pervious checks succeeded, then the caller is unrecognized.
else -> false
}
if (!isCallerKnown) {
logUnknownCaller(callerPackageInfo)
}
// Save our work for next time.
callerChecked[callingPackage] = Pair(callingUid, isCallerKnown)
return isCallerKnown
}
/**
* Logs an info level message with details of how to add a caller to the allowed callers list
* when the app is debuggable.
*/
private fun logUnknownCaller(callerPackageInfo: CallerPackageInfo) {
if (BuildConfig.DEBUG && callerPackageInfo.signature != null) {
Log.i(TAG, "PackageValidator call" + callerPackageInfo.name + callerPackageInfo.packageName + callerPackageInfo.signature)
}
}
/**
* Builds a [CallerPackageInfo] for a given package that can be used for all the
* various checks that are performed before allowing an app to connect to a
* [MediaBrowserServiceCompat].
*/
private fun buildCallerInfo(callingPackage: String): CallerPackageInfo? {
val packageInfo = getPackageInfo(callingPackage) ?: return null
val appName = packageInfo.applicationInfo.loadLabel(packageManager).toString()
val uid = packageInfo.applicationInfo.uid
val signature = getSignature(packageInfo)
val requestedPermissions = packageInfo.requestedPermissions
val permissionFlags = packageInfo.requestedPermissionsFlags
val activePermissions = mutableSetOf<String>()
requestedPermissions?.forEachIndexed { index, permission ->
if (permissionFlags[index] and REQUESTED_PERMISSION_GRANTED != 0) {
activePermissions += permission
}
}
return CallerPackageInfo(appName, callingPackage, uid, signature, activePermissions.toSet())
}
/**
* Looks up the [PackageInfo] for a package name.
* This requests both the signatures (for checking if an app is on the whitelist) and
* the app's permissions, which allow for more flexibility in the whitelist.
*
* @return [PackageInfo] for the package name or null if it's not found.
*/
@SuppressLint("PackageManagerGetSignatures")
private fun getPackageInfo(callingPackage: String): PackageInfo? =
packageManager.getPackageInfo(callingPackage,
PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS)
/**
* Gets the signature of a given package's [PackageInfo].
*
* The "signature" is a SHA-256 hash of the public key of the signing certificate used by
* the app.
*
* If the app is not found, or if the app does not have exactly one signature, this method
* returns `null` as the signature.
*/
private fun getSignature(packageInfo: PackageInfo): String? {
// Security best practices dictate that an app should be signed with exactly one (1)
// signature. Because of this, if there are multiple signatures, reject it.
if (packageInfo.signatures == null || packageInfo.signatures.size != 1) {
return null
} else {
val certificate = packageInfo.signatures[0].toByteArray()
return getSignatureSha256(certificate)
}
}
private fun buildCertificateWhitelist(parser: XmlResourceParser): Map<String, KnownCallerInfo> {
val certificateWhitelist = LinkedHashMap<String, KnownCallerInfo>()
try {
var eventType = parser.next()
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
val callerInfo = when (parser.name) {
"signing_certificate" -> parseV1Tag(parser)
"signature" -> parseV2Tag(parser)
else -> null
}
callerInfo?.let { info ->
val packageName = info.packageName
val existingCallerInfo = certificateWhitelist[packageName]
if (existingCallerInfo != null) {
existingCallerInfo.signatures += callerInfo.signatures
} else {
certificateWhitelist[packageName] = callerInfo
}
}
}
eventType = parser.next()
}
} catch (xmlException: XmlPullParserException) {
Log.e(TAG, "Could not read allowed callers from XML.", xmlException)
} catch (ioException: IOException) {
Log.e(TAG, "Could not read allowed callers from XML.", ioException)
}
return certificateWhitelist
}
/**
* Parses a v1 format tag. See allowed_media_browser_callers.xml for more details.
*/
private fun parseV1Tag(parser: XmlResourceParser): KnownCallerInfo {
val name = parser.getAttributeValue(null, "name")
val packageName = parser.getAttributeValue(null, "package")
val isRelease = parser.getAttributeBooleanValue(null, "release", false)
val certificate = parser.nextText().replace(WHITESPACE_REGEX, "")
val signature = getSignatureSha256(certificate)
val callerSignature = KnownSignature(signature, isRelease)
return KnownCallerInfo(name, packageName, mutableSetOf(callerSignature))
}
/**
* Parses a v2 format tag. See allowed_media_browser_callers.xml for more details.
*/
private fun parseV2Tag(parser: XmlResourceParser): KnownCallerInfo {
val name = parser.getAttributeValue(null, "name")
val packageName = parser.getAttributeValue(null, "package")
val callerSignatures = mutableSetOf<KnownSignature>()
var eventType = parser.next()
while (eventType != XmlResourceParser.END_TAG) {
val isRelease = parser.getAttributeBooleanValue(null, "release", false)
val signature = parser.nextText().replace(WHITESPACE_REGEX, "").toLowerCase()
callerSignatures += KnownSignature(signature, isRelease)
eventType = parser.next()
}
return KnownCallerInfo(name, packageName, callerSignatures)
}
/**
* Finds the Android platform signing key signature. This key is never null.
*/
private fun getSystemSignature(): String =
getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo ->
getSignature(platformInfo)
} ?: throw IllegalStateException("Platform signature not found")
/**
* Creates a SHA-256 signature given a Base64 encoded certificate.
*/
private fun getSignatureSha256(certificate: String): String {
return getSignatureSha256(Base64.decode(certificate, Base64.DEFAULT))
}
/**
* Creates a SHA-256 signature given a certificate byte array.
*/
private fun getSignatureSha256(certificate: ByteArray): String {
val md: MessageDigest
try {
md = MessageDigest.getInstance("SHA256")
} catch (noSuchAlgorithmException: NoSuchAlgorithmException) {
Log.e(TAG, "No such algorithm: $noSuchAlgorithmException")
throw RuntimeException("Could not find SHA256 hash algorithm", noSuchAlgorithmException)
}
md.update(certificate)
// This code takes the byte array generated by `md.digest()` and joins each of the bytes
// to a string, applying the string format `%02x` on each digit before it's appended, with
// a colon (':') between each of the items.
// For example: input=[0,2,4,6,8,10,12], output="00:02:04:06:08:0a:0c"
return md.digest().joinToString(":") { String.format("%02x", it) }
}
private data class KnownCallerInfo(
internal val name: String,
internal val packageName: String,
internal val signatures: MutableSet<KnownSignature>
)
private data class KnownSignature(
internal val signature: String,
internal val release: Boolean
)
/**
* Convenience class to hold all of the information about an app that's being checked
* to see if it's a known caller.
*/
private data class CallerPackageInfo(
internal val name: String,
internal val packageName: String,
internal val uid: Int,
internal val signature: String?,
internal val permissions: Set<String>
)
}
private const val TAG = "PackageValidator"
private const val ANDROID_PLATFORM = "android"
private val WHITESPACE_REGEX = "\\s|\\n".toRegex()

View File

@ -21,7 +21,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
@ -31,9 +30,6 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.ResultReceiver;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.Display; import android.view.Display;
import android.view.View; import android.view.View;
@ -45,16 +41,8 @@ import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.TintHelper; import code.name.monkey.appthemehelper.util.TintHelper;
import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.App;
@ -88,40 +76,6 @@ public class RetroUtil {
return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
} }
@TargetApi(19)
public static void setStatusBarTranslucent(@NonNull Window window) {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
public static boolean isMarshMellow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
public static boolean isNougat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
public static boolean isOreo() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public static float getDistance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
public static float convertDpToPixel(float dp, @NonNull Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
public static float convertPixelsToDp(float px, @NonNull Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
public static void openUrl(@NonNull Activity context, @NonNull String str) { public static void openUrl(@NonNull Activity context, @NonNull String str) {
Intent intent = new Intent("android.intent.action.VIEW"); Intent intent = new Intent("android.intent.action.VIEW");
intent.setData(Uri.parse(str)); intent.setData(Uri.parse(str));
@ -154,20 +108,6 @@ public class RetroUtil {
} }
} }
public static void showIme(@NonNull View view) {
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService
(Context.INPUT_METHOD_SERVICE);
// the public methods don't seem to work for me, so… reflection.
try {
Method showSoftInputUnchecked = InputMethodManager.class.getMethod(
"showSoftInputUnchecked", int.class, ResultReceiver.class);
showSoftInputUnchecked.setAccessible(true);
showSoftInputUnchecked.invoke(imm, 0, null);
} catch (Exception e) {
// ho hum
}
}
@Nullable @Nullable
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId, public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
@Nullable Resources.Theme theme) { @Nullable Resources.Theme theme) {
@ -184,17 +124,6 @@ public class RetroUtil {
getVectorDrawable(context.getResources(), id, context.getTheme()), color); getVectorDrawable(context.getResources(), id, context.getTheme()), color);
} }
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int id,
@ColorInt int color) {
return TintHelper.createTintedDrawable(ContextCompat.getDrawable(context, id), color);
}
public static Drawable getTintedDrawable(@DrawableRes int id) {
return TintHelper
.createTintedDrawable(ContextCompat.getDrawable(App.Companion.getContext(), id),
ThemeStore.Companion.accentColor(App.Companion.getContext()));
}
@NonNull @NonNull
public static Bitmap createBitmap(@NonNull Drawable drawable, float sizeMultiplier) { public static Bitmap createBitmap(@NonNull Drawable drawable, float sizeMultiplier) {
Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier),
@ -227,64 +156,6 @@ public class RetroUtil {
} }
} }
public static String getIPAddress(boolean useIPv4) {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress()) {
String sAddr = addr.getHostAddress();
//boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);
boolean isIPv4 = sAddr.indexOf(':') < 0;
if (useIPv4) {
if (isIPv4)
return sAddr;
} else {
if (!isIPv4) {
int delim = sAddr.indexOf('%'); // drop ip6 zone suffix
return delim < 0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).toUpperCase();
}
}
}
}
}
} catch (Exception ex) {
}
return "";
}
public static Uri getSongUri(Context context, long id) {
final String[] projection = new String[]{
BaseColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.Audio.AudioColumns.ALBUM_ID
};
final StringBuilder selection = new StringBuilder();
selection.append(BaseColumns._ID + " IN (");
selection.append(id);
selection.append(")");
final Cursor c = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null);
if (c == null) {
return null;
}
c.moveToFirst();
try {
Uri uri = Uri.parse(c.getString(1));
c.close();
return uri;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static int getStatusBarHeight() { public static int getStatusBarHeight() {
int result = 0; int result = 0;
int resourceId = App.Companion.getContext().getResources().getIdentifier("status_bar_height", "dimen", "android"); int resourceId = App.Companion.getContext().getResources().getIdentifier("status_bar_height", "dimen", "android");

View File

@ -15,6 +15,8 @@
package code.name.monkey.retromusic.util; package code.name.monkey.retromusic.util;
import android.graphics.Canvas; import android.graphics.Canvas;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
@ -22,7 +24,7 @@ public class SwipeAndDragHelper extends ItemTouchHelper.Callback {
private ActionCompletionContract contract; private ActionCompletionContract contract;
public SwipeAndDragHelper(ActionCompletionContract contract) { public SwipeAndDragHelper(@NonNull ActionCompletionContract contract) {
this.contract = contract; this.contract = contract;
} }

View File

@ -28,41 +28,18 @@ import android.view.View
import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import com.google.android.material.card.MaterialCardView
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
object ViewUtil { object ViewUtil {
const val RETRO_MUSIC_ANIM_TIME = 1000 const val RETRO_MUSIC_ANIM_TIME = 1000
fun cardViewTopCorners(cardView: MaterialCardView) {
val radius = PreferenceUtil.getInstance(cardView.context).dialogCorner
(cardView.background as? MaterialShapeDrawable).let {
it?.shapeAppearanceModel?.apply {
toBuilder()
.setTopLeftCorner(CornerFamily.CUT, 10f)
.build()
}
}
}
fun createTextColorTransition(v: TextView, @ColorInt startColor: Int, @ColorInt endColor: Int): Animator {
return createColorAnimator(v, "textColor", startColor, endColor)
}
fun createBackgroundColorTransition(v: View, @ColorInt startColor: Int, @ColorInt endColor: Int): Animator {
return createColorAnimator(v, "backgroundColor", startColor, endColor)
}
fun setProgressDrawable(progressSlider: SeekBar, newColor: Int, thumbTint: Boolean = false) { fun setProgressDrawable(progressSlider: SeekBar, newColor: Int, thumbTint: Boolean = false) {
@ -103,21 +80,6 @@ object ViewUtil {
return animator return animator
} }
fun setStatusBarHeight(context: Context, statusBar: View) {
val lp = statusBar.layoutParams
lp.height = getStatusBarHeight(context)
statusBar.requestLayout()
}
fun getStatusBarHeight(context: Context): Int {
var result = 0
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = context.resources.getDimensionPixelSize(resourceId)
}
return result
}
fun hitTest(v: View, x: Int, y: Int): Boolean { fun hitTest(v: View, x: Int, y: Int): Boolean {
val tx = (ViewCompat.getTranslationX(v) + 0.5f).toInt() val tx = (ViewCompat.getTranslationX(v) + 0.5f).toInt()
val ty = (ViewCompat.getTranslationY(v) + 0.5f).toInt() val ty = (ViewCompat.getTranslationY(v) + 0.5f).toInt()

View File

@ -1,91 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util.color;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
/**
* A utility class to colorize bitmaps with a color gradient and a special blending mode
*/
public class ImageGradientColorizer {
public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
int size = Math.min(width, height);
int widthInset = (width - size) / 2;
int heightInset = (height - size) / 2;
drawable = drawable.mutate();
drawable.setBounds(-widthInset, -heightInset, width - widthInset, height - heightInset);
Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
// Values to calculate the luminance of a color
float lr = 0.2126f;
float lg = 0.7152f;
float lb = 0.0722f;
// Extract the red, green, blue components of the color extraction color in
// float and int form
int tri = Color.red(backgroundColor);
int tgi = Color.green(backgroundColor);
int tbi = Color.blue(backgroundColor);
float tr = tri / 255f;
float tg = tgi / 255f;
float tb = tbi / 255f;
// Calculate the luminance of the color extraction color
float cLum = (tr * lr + tg * lg + tb * lb) * 255;
ColorMatrix m = new ColorMatrix(new float[]{
lr, lg, lb, 0, tri - cLum,
lr, lg, lb, 0, tgi - cLum,
lr, lg, lb, 0, tbi - cLum,
0, 0, 0, 1, 0,
});
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
LinearGradient linearGradient = new LinearGradient(0, 0, size, 0,
new int[]{0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
new float[]{0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas fadeInCanvas = new Canvas(fadeIn);
drawable.clearColorFilter();
drawable.draw(fadeInCanvas);
if (isRtl) {
// Let's flip the gradient
fadeInCanvas.translate(size, 0);
fadeInCanvas.scale(-1, 1);
}
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
fadeInCanvas.drawPaint(paint);
Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
coloredPaint.setColorFilter(new ColorMatrixColorFilter(m));
coloredPaint.setAlpha((int) (0.5f * 255));
canvas.drawBitmap(fadeIn, 0, 0, coloredPaint);
linearGradient = new LinearGradient(0, 0, size, 0,
new int[]{0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
new float[]{0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
fadeInCanvas.drawPaint(paint);
canvas.drawBitmap(fadeIn, 0, 0, null);
return newBitmap;
}
}

View File

@ -20,6 +20,7 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.palette.graphics.Palette; import androidx.palette.graphics.Palette;
@ -57,7 +58,6 @@ public class MediaNotificationProcessor {
private static final float BLACK_MAX_LIGHTNESS = 0.08f; private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f; private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150; private static final int RESIZE_BITMAP_AREA = 150 * 150;
private final ImageGradientColorizer mColorizer;
private final Context mContext; private final Context mContext;
/** /**
* The context of the notification. This is the app context of the package posting the * The context of the notification. This is the app context of the package posting the
@ -70,16 +70,14 @@ public class MediaNotificationProcessor {
private onColorThing onColorThing; private onColorThing onColorThing;
public MediaNotificationProcessor(Context context, Context packageContext, onColorThing thing) { public MediaNotificationProcessor(Context context, Context packageContext, onColorThing thing) {
this(context, packageContext, new ImageGradientColorizer()); this(context, packageContext);
onColorThing = thing; onColorThing = thing;
} }
@VisibleForTesting @VisibleForTesting
MediaNotificationProcessor(Context context, Context packageContext, MediaNotificationProcessor(Context context, Context packageContext) {
ImageGradientColorizer colorizer) {
mContext = context; mContext = context;
mPackageContext = packageContext; mPackageContext = packageContext;
mColorizer = colorizer;
} }
/** /**
@ -292,228 +290,4 @@ public class MediaNotificationProcessor {
public interface onColorThing { public interface onColorThing {
void bothColor(int i, int i2); void bothColor(int i, int i2);
} }
/**
* 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 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 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 float[] mFilteredBackgroundHsl = null;
private final ImageGradientColorizer mColorizer;
private final Context mContext;
*//**
* The context of the notification. This is the app context of the package posting the
* notification.
*//*
private final Context mPackageContext;
private static Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
private boolean mIsLowPriority;
public MediaNotificationProcessor(Context context, Context packageContext) {
this(context, packageContext, new ImageGradientColorizer());
}
@VisibleForTesting
MediaNotificationProcessor(Context context, Context packageContext,
ImageGradientColorizer colorizer) {
mContext = context;
mPackageContext = packageContext;
mColorizer = colorizer;
}
@Nullable
public static Palette.Builder generatePalette(Bitmap bitmap) {
return bitmap == null ? null : Palette.from(bitmap).clearFilters().resizeBitmapArea(RESIZE_BITMAP_AREA);
}
public static int getBackgroundColor(Palette.Builder builder) {
return findBackgroundColorAndFilter(builder.generate());
}
public static int getTextColor(Palette.Builder builder) {
int backgroundColor = 0;
if (mFilteredBackgroundHsl != null) {
builder.addFilter((rgb, hsl) -> {
// at least 10 degrees hue difference
float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]);
return diff > 10 && diff < 350;
});
}
builder.addFilter(mBlackWhiteFilter);
Palette palette = builder.generate();
return selectForegroundColor(backgroundColor, palette);
}
private static int selectForegroundColor(int backgroundColor, Palette palette) {
if (ColorUtil.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);
}
}
private static 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 static 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 static 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 static 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 static int findBackgroundColorAndFilter(Palette palette) {
// 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<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 static boolean isWhiteOrBlack(float[] hsl) {
return isBlack(hsl) || isWhite(hsl);
}
*//**
* @return true if the color represents a color which is close to black.
*//*
private static boolean isBlack(float[] hslColor) {
return hslColor[2] <= BLACK_MAX_LIGHTNESS;
}
*//**
* @return true if the color represents a color which is close to white.
*//*
private static boolean isWhite(float[] hslColor) {
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
}
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
}*/
} }

View File

@ -1,33 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util.schedulers;
import androidx.annotation.NonNull;
import io.reactivex.Scheduler;
/**
* Created by hemanths on 12/08/17.
*/
public interface BaseSchedulerProvider {
@NonNull
Scheduler computation();
@NonNull
Scheduler io();
@NonNull
Scheduler ui();
}

View File

@ -1,58 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util.schedulers;
import androidx.annotation.NonNull;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* Created by hemanths on 12/08/17.
*/
public class SchedulerProvider implements BaseSchedulerProvider {
@NonNull
private static SchedulerProvider INSTANCE;
public SchedulerProvider() {
}
public static synchronized SchedulerProvider getInstance() {
if (INSTANCE == null) {
INSTANCE = new SchedulerProvider();
}
return INSTANCE;
}
@Override
@NonNull
public Scheduler computation() {
return Schedulers.computation();
}
@Override
@NonNull
public Scheduler io() {
return Schedulers.io();
}
@Override
@NonNull
public Scheduler ui() {
return AndroidSchedulers.mainThread();
}
}

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<gradient
android:angle="0"
android:centerColor="#80ffffff"
android:endColor="@android:color/transparent"
android:startColor="@android:color/white"
android:type="linear"/>
<gradient
android:angle="0"
android:centerColor="#80ffffff"
android:endColor="@android:color/transparent"
android:startColor="?colorPrimary"
android:type="linear" />
</shape> </shape>

View File

@ -1,157 +1,159 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/background" android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="ContentDescription">
<RelativeLayout
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@+id/content"
android:layout_alignParentEnd="true">
<ImageView
android:id="@+id/largeIcon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
/>
<ImageView
android:id="@+id/foregroundImage"
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_alignStart="@id/largeIcon"
android:src="@drawable/background_image"
tools:tint="@color/md_black_1000"/>
</RelativeLayout>
<LinearLayout
android:id="@+id/app"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginEnd="48dp"
android:paddingTop="6dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/smallIcon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
tools:src="@drawable/ic_audiotrack_black_24dp"
tools:tint="@color/md_black_1000"/>
<TextView
android:id="@+id/appName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:textSize="12sp"
tools:text="@string/app_name"/>
<ImageView
android:id="@+id/arrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="2dp"
android:scaleType="centerInside"
android:src="@drawable/ic_keyboard_arrow_down_black_24dp"
android:tint="@color/md_black_1000"
tools:ignore="VectorDrawableCompat"/>
</LinearLayout>
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" tools:ignore="ContentDescription">
android:layout_below="@id/app"
android:layout_toStartOf="@id/actions"
android:paddingBottom="12dp"
android:paddingStart="0dp"
android:paddingEnd="12dp"
android:orientation="vertical">
<TextView <RelativeLayout
android:id="@+id/title" android:id="@+id/image"
android:textStyle="bold" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_height="wrap_content" android:layout_alignBottom="@+id/content"
android:paddingStart="16dp" android:layout_alignParentEnd="true">
android:paddingEnd="0dp"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
tools:text="@string/title_dashboard"/>
<TextView <ImageView
android:id="@+id/subtitle" android:id="@+id/largeIcon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:paddingStart="16dp" android:layout_alignParentEnd="true"
android:paddingEnd="0dp" android:adjustViewBounds="true"
android:ellipsize="end" android:scaleType="centerCrop"
android:lines="1" tools:src="@tools:sample/avatars" />
android:singleLine="true"
tools:text="@string/title_dashboard"/>
</LinearLayout> <ImageView
android:id="@+id/foregroundImage"
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_alignStart="@id/largeIcon"
android:src="@drawable/background_image"
tools:tint="?colorPrimary" />
<LinearLayout </RelativeLayout>
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="48dp"
android:layout_alignBottom="@id/content"
android:layout_alignParentEnd="true"
android:layout_alignTop="@id/content"
android:layout_gravity="bottom"
android:orientation="horizontal">
<ImageView <LinearLayout
android:id="@+id/action_prev" android:id="@+id/app"
android:layout_width="42dp" android:layout_width="wrap_content"
android:layout_height="42dp" android:layout_height="wrap_content"
android:scaleType="centerInside"/> android:layout_marginEnd="48dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp">
<ImageView <ImageView
android:id="@+id/action_play_pause" android:id="@+id/smallIcon"
android:layout_width="42dp" android:layout_width="12dp"
android:layout_height="42dp" android:layout_height="12dp"
android:scaleType="centerInside"/> android:layout_marginStart="8dp"
tools:src="@drawable/ic_audiotrack_black_24dp"
tools:tint="@color/md_black_1000" />
<ImageView <TextView
android:id="@+id/action_next" android:id="@+id/appName"
android:layout_width="42dp" android:layout_width="wrap_content"
android:layout_height="42dp" android:layout_height="wrap_content"
android:scaleType="centerInside"/> android:layout_marginStart="4dp"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:textSize="10sp"
tools:text="@string/app_name" />
<ImageView <ImageView
android:id="@+id/action_quit" android:id="@+id/arrow"
android:layout_width="42dp" android:layout_width="12dp"
android:layout_height="42dp" android:layout_height="12dp"
android:scaleType="centerInside" android:layout_marginStart="2dp"
android:visibility="gone"/> android:scaleType="centerInside"
android:src="@drawable/ic_keyboard_arrow_down_black_24dp"
android:tint="@color/md_black_1000"
tools:ignore="VectorDrawableCompat" />
<ImageView </LinearLayout>
android:id="@+id/fifth"
android:layout_width="42dp" <LinearLayout
android:layout_height="42dp" android:id="@+id/content"
android:scaleType="centerInside"/> android:layout_width="match_parent"
</LinearLayout> android:layout_height="wrap_content"
android:layout_below="@id/app"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/actions"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:textSize="12sp"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/content"
android:layout_alignBottom="@id/content"
android:layout_alignParentEnd="true"
android:layout_gravity="bottom"
android:layout_marginEnd="24dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/action_prev"
android:layout_width="38dp"
android:layout_height="38dp"
android:scaleType="centerInside"
android:src="@drawable/ic_skip_previous_round_white_32dp"
tools:tint="?colorOnPrimary" />
<ImageView
android:id="@+id/action_play_pause"
android:layout_width="38dp"
android:layout_height="38dp"
android:scaleType="centerInside"
android:src="@drawable/ic_pause_white_24dp"
tools:tint="?colorOnPrimary" />
<ImageView
android:id="@+id/action_next"
android:layout_width="38dp"
android:layout_height="38dp"
android:scaleType="centerInside"
android:src="@drawable/ic_skip_next_round_white_32dp"
tools:tint="?colorOnPrimary" />
<ImageView
android:id="@+id/action_quit"
android:layout_width="38dp"
android:layout_height="38dp"
android:scaleType="centerInside"
android:visibility="gone" />
<ImageView
android:id="@+id/fifth"
android:layout_width="38dp"
android:layout_height="38dp"
android:scaleType="centerInside" />
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -1,146 +1,135 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/background" android:id="@+id/background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<RelativeLayout <RelativeLayout
android:id="@+id/image" android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_alignBottom="@+id/content" android:layout_alignBottom="@+id/content"
android:layout_alignParentEnd="true"> android:layout_alignParentEnd="true">
<ImageView <ImageView
android:id="@+id/largeIcon" android:id="@+id/largeIcon"
android:layout_width="@dimen/notification_big_image_size" android:layout_width="@dimen/notification_big_image_size"
android:layout_height="match_parent" android:layout_height="@dimen/notification_big_image_size"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:scaleType="centerCrop"
android:scaleType="centerCrop" tools:src="@tools:sample/avatars" />
android:contentDescription="TODO" />
<ImageView <ImageView
android:id="@+id/foregroundImage" android:id="@+id/foregroundImage"
android:layout_width="96dp" android:layout_width="96dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignStart="@id/largeIcon" android:layout_alignStart="@id/largeIcon"
android:src="@drawable/background_image" android:src="@drawable/background_image" />
android:tint="@color/md_black_1000"
android:contentDescription="TODO" />
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout
android:id="@+id/content" android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:paddingTop="8dp" android:orientation="vertical"
android:paddingBottom="12dp" android:paddingStart="0dp"
android:paddingStart="0dp" android:paddingTop="8dp"
android:paddingEnd="144dp" android:paddingEnd="144dp">
android:orientation="vertical">
<LinearLayout <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:paddingStart="8dp"
android:paddingEnd="0dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/smallIcon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
tools:src="@drawable/ic_audiotrack_black_24dp"
tools:tint="@color/md_black_1000"
android:contentDescription="TODO" />
<TextView
android:id="@+id/appName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginBottom="4dp"
android:ellipsize="end" android:gravity="center_vertical"
android:lines="1" android:orientation="horizontal">
android:singleLine="true"
android:textSize="13sp"
tools:text="@string/app_name"/>
<ImageView <ImageView
android:id="@+id/arrow" android:id="@+id/smallIcon"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:layout_marginStart="2dp" android:layout_marginStart="8dp"
android:scaleType="centerInside" tools:src="@drawable/ic_audiotrack_black_24dp"
android:src="@drawable/ic_keyboard_arrow_up_24dp" tools:tint="@color/md_black_1000" />
tools:ignore="VectorDrawableCompat"
android:contentDescription="TODO" /> <TextView
android:id="@+id/appName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:textSize="13sp"
tools:text="@string/app_name" />
<ImageView
android:id="@+id/arrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="2dp"
android:scaleType="centerInside"
android:src="@drawable/ic_keyboard_arrow_up_24dp"
android:tint="@color/dark_grey"
tools:ignore="VectorDrawableCompat" />
</LinearLayout> </LinearLayout>
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:textStyle="bold" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:ellipsize="end"
android:paddingStart="16dp" android:lines="1"
android:paddingEnd="0dp" android:paddingStart="16dp"
android:ellipsize="end" android:paddingEnd="0dp"
android:lines="1" android:singleLine="true"
android:singleLine="true" android:textStyle="bold"
tools:text="@string/title_dashboard"/> tools:text="@tools:sample/lorem/random" />
<TextView <TextView
android:id="@+id/subtitle" android:id="@+id/subtitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="16dp" android:ellipsize="end"
android:paddingEnd="0dp" android:lines="1"
android:ellipsize="end" android:paddingStart="16dp"
android:lines="1" android:paddingEnd="0dp"
android:singleLine="true" android:singleLine="true"
tools:text="@string/title_dashboard"/> tools:text="@tools:sample/lorem/random" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:paddingEnd="0dp"
android:layout_height="match_parent" android:paddingStart="12dp"
android:layout_marginStart="8dp" android:layout_width="wrap_content"
android:layout_marginEnd="8dp" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="horizontal">
tools:ignore="ContentDescription">
<ImageView <ImageView
android:id="@+id/action_prev" android:id="@+id/action_prev"
android:layout_width="48dp" android:layout_width="42dp"
android:layout_height="48dp" android:layout_height="42dp"
android:scaleType="centerInside" android:scaleType="centerInside" />
tools:tint="@color/md_black_1000"/>
<ImageView <ImageView
android:id="@+id/action_play_pause" android:id="@+id/action_play_pause"
android:layout_width="48dp" android:layout_width="42dp"
android:layout_height="48dp" android:layout_height="42dp"
android:scaleType="centerInside"/> android:scaleType="centerInside" />
<ImageView <ImageView
android:id="@+id/action_next" android:id="@+id/action_next"
android:layout_width="48dp" android:layout_width="42dp"
android:layout_height="48dp" android:layout_height="42dp"
android:scaleType="centerInside"/> android:scaleType="centerInside" />
<ImageView <ImageView
android:id="@+id/action_quit" android:id="@+id/action_quit"
android:layout_width="48dp" android:layout_width="42dp"
android:layout_height="48dp" android:layout_height="42dp"
android:scaleType="centerInside" android:scaleType="centerInside" />
tools:ignore="ContentDescription"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -19,7 +19,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" /> android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
<ImageView <ImageView
android:id="@+id/foregroundImage" android:id="@+id/foregroundImage"
@ -27,7 +28,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignStart="@id/largeIcon" android:layout_alignStart="@id/largeIcon"
android:src="@drawable/background_image" android:src="@drawable/background_image"
tools:tint="@color/md_black_1000" /> tools:tint="?colorPrimary" />
</RelativeLayout> </RelativeLayout>
@ -36,11 +37,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="48dp" android:layout_marginEnd="48dp"
android:layout_marginBottom="4dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingTop="6dp" android:paddingTop="4dp"
android:paddingEnd="8dp"> android:paddingEnd="8dp">
<ImageView <ImageView
@ -82,9 +82,8 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_toStartOf="@id/actions" android:layout_toStartOf="@id/actions"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="0dp" android:paddingStart="16dp"
android:paddingEnd="12dp" android:paddingEnd="12dp">
android:paddingBottom="12dp">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
@ -92,11 +91,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:singleLine="true" android:singleLine="true"
android:textStyle="bold" android:textStyle="bold"
tools:text="@string/title_dashboard" /> tools:text="@tools:sample/lorem/random" />
<TextView <TextView
android:id="@+id/subtitle" android:id="@+id/subtitle"
@ -104,10 +101,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:singleLine="true" android:singleLine="true"
tools:text="@string/title_dashboard" /> tools:text="@tools:sample/lorem/random" />
</LinearLayout> </LinearLayout>
@ -124,33 +119,39 @@
<ImageView <ImageView
android:id="@+id/action_prev" android:id="@+id/action_prev"
android:layout_width="42dp" android:layout_width="38dp"
android:layout_height="42dp" android:layout_height="38dp"
android:scaleType="centerInside" /> android:scaleType="centerInside"
android:src="@drawable/ic_skip_previous_round_white_32dp"
android:tint="?colorOnPrimary" />
<ImageView <ImageView
android:id="@+id/action_play_pause" android:id="@+id/action_play_pause"
android:layout_width="42dp" android:layout_width="38dp"
android:layout_height="42dp" android:layout_height="38dp"
android:scaleType="centerInside" /> android:scaleType="centerInside"
android:src="@drawable/ic_pause_white_24dp"
android:tint="?colorOnPrimary" />
<ImageView <ImageView
android:id="@+id/action_next" android:id="@+id/action_next"
android:layout_width="42dp" android:layout_width="38dp"
android:layout_height="42dp" android:layout_height="38dp"
android:scaleType="centerInside" /> android:scaleType="centerInside"
android:src="@drawable/ic_skip_next_round_white_32dp"
android:tint="?colorOnPrimary" />
<ImageView <ImageView
android:id="@+id/action_quit" android:id="@+id/action_quit"
android:layout_width="42dp" android:layout_width="38dp"
android:layout_height="42dp" android:layout_height="38dp"
android:scaleType="centerInside" android:scaleType="centerInside"
android:visibility="gone" /> android:visibility="gone" />
<ImageView <ImageView
android:id="@+id/fifth" android:id="@+id/fifth"
android:layout_width="42dp" android:layout_width="38dp"
android:layout_height="42dp" android:layout_height="38dp"
android:scaleType="centerInside" /> android:scaleType="centerInside" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -1,142 +1,133 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/background" android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:layout_alignBottom="@+id/content"
android:layout_alignParentEnd="true">
<ImageView <RelativeLayout
android:id="@+id/largeIcon" android:id="@+id/image"
android:layout_width="@dimen/notification_big_image_size" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:contentDescription="TODO" />
<ImageView
android:id="@+id/foregroundImage"
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_alignStart="@id/largeIcon"
android:src="@drawable/background_image"
tools:tint="@color/md_black_1000"
android:contentDescription="TODO" />
</RelativeLayout>
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:paddingTop="8dp"
android:paddingBottom="12dp"
android:paddingStart="0dp"
android:paddingEnd="144dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/smallIcon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
tools:src="@drawable/ic_audiotrack_black_24dp"
tools:tint="@color/md_black_1000"
android:contentDescription="TODO" />
<TextView
android:id="@+id/appName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_alignBottom="@+id/content"
android:ellipsize="end" android:layout_alignParentEnd="true">
android:lines="1"
android:singleLine="true"
android:textSize="13sp"
tools:text="@string/app_name"/>
<ImageView <ImageView
android:id="@+id/arrow" android:id="@+id/largeIcon"
android:layout_width="16dp" android:layout_width="@dimen/notification_big_image_size"
android:layout_height="16dp" android:layout_height="@dimen/notification_big_image_size"
android:layout_marginStart="2dp" android:layout_alignParentEnd="true"
android:scaleType="centerInside" android:scaleType="centerCrop"
android:src="@drawable/ic_keyboard_arrow_up_24dp" tools:src="@tools:sample/avatars" />
tools:ignore="VectorDrawableCompat"
android:contentDescription="TODO" />
</LinearLayout> <ImageView
android:id="@+id/foregroundImage"
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_alignStart="@id/largeIcon"
android:src="@drawable/background_image" />
<TextView </RelativeLayout>
android:id="@+id/title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
tools:text="@string/title_dashboard"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
tools:text="@string/title_dashboard"/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:id="@+id/content"
android:layout_height="match_parent" android:layout_width="match_parent"
android:orientation="horizontal"> android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:orientation="vertical"
android:paddingStart="0dp"
android:paddingTop="8dp"
android:paddingEnd="144dp">
<ImageView <LinearLayout
android:id="@+id/action_prev" android:layout_width="wrap_content"
android:layout_width="42dp" android:layout_height="wrap_content"
android:layout_height="42dp" android:layout_marginBottom="4dp"
android:scaleType="centerInside" android:gravity="center_vertical"
android:contentDescription="TODO" /> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/action_play_pause" android:id="@+id/smallIcon"
android:layout_width="42dp" android:layout_width="16dp"
android:layout_height="42dp" android:layout_height="16dp"
android:scaleType="centerInside" android:layout_marginStart="8dp"
android:contentDescription="TODO" /> tools:src="@drawable/ic_audiotrack_black_24dp"
tools:tint="@color/md_black_1000" />
<ImageView <TextView
android:id="@+id/action_next" android:id="@+id/appName"
android:layout_width="42dp" android:layout_width="wrap_content"
android:layout_height="42dp" android:layout_height="wrap_content"
android:scaleType="centerInside" android:layout_marginStart="4dp"
android:contentDescription="TODO" /> android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:textSize="13sp"
tools:text="@string/app_name" />
<ImageView <ImageView
android:id="@+id/action_quit" android:id="@+id/arrow"
android:layout_width="42dp" android:layout_width="16dp"
android:layout_height="42dp" android:layout_height="16dp"
android:scaleType="centerInside" android:layout_marginStart="2dp"
android:contentDescription="TODO" /> android:scaleType="centerInside"
android:src="@drawable/ic_keyboard_arrow_up_24dp"
android:tint="@color/dark_grey"
tools:ignore="VectorDrawableCompat" />
</LinearLayout>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:singleLine="true"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:singleLine="true"
tools:text="@tools:sample/lorem/random" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/action_prev"
android:layout_width="42dp"
android:layout_height="42dp"
android:scaleType="centerInside" />
<ImageView
android:id="@+id/action_play_pause"
android:layout_width="42dp"
android:layout_height="42dp"
android:scaleType="centerInside" />
<ImageView
android:id="@+id/action_next"
android:layout_width="42dp"
android:layout_height="42dp"
android:scaleType="centerInside" />
<ImageView
android:id="@+id/action_quit"
android:layout_width="42dp"
android:layout_height="42dp"
android:scaleType="centerInside" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -18,7 +18,7 @@
<dimen name="empty_text_size">20sp</dimen> <dimen name="empty_text_size">20sp</dimen>
<dimen name="notification_big_image_size">134dp</dimen> <dimen name="notification_big_image_size">112dp</dimen>
<!-- MUST BE THE RESULT OF WIDTH PLUS INSET--> <!-- MUST BE THE RESULT OF WIDTH PLUS INSET-->