Rx Java and Normal compatible for all query
This commit is contained in:
parent
850036e5cc
commit
c2759e3ec0
89 changed files with 2900 additions and 1040 deletions
|
@ -20,6 +20,9 @@ import android.os.Environment;
|
|||
import android.provider.MediaStore;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -34,12 +37,9 @@ import java.util.Collections;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||
import code.name.monkey.retromusic.loaders.SortedCursor;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
|
||||
public final class FileUtil {
|
||||
|
@ -57,9 +57,10 @@ public final class FileUtil {
|
|||
stream.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Observable<ArrayList<Song>> matchFilesWithMediaStore(@NonNull Context context,
|
||||
@Nullable List<File> files) {
|
||||
public static ArrayList<Song> matchFilesWithMediaStore(@NonNull Context context,
|
||||
@Nullable List<File> files) {
|
||||
return SongLoader.INSTANCE.getSongs(makeSongCursor(context, files));
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
package code.name.monkey.retromusic.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
|
@ -21,13 +23,22 @@ import android.graphics.Matrix;
|
|||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
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 java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.TintHelper;
|
||||
|
||||
/**
|
||||
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub :
|
||||
* https://github.com/zetbaitsu
|
||||
|
@ -57,6 +68,37 @@ public class ImageUtil {
|
|||
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}.
|
||||
*/
|
||||
|
|
|
@ -51,7 +51,6 @@ import code.name.monkey.retromusic.model.Genre;
|
|||
import code.name.monkey.retromusic.model.Playlist;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
|
||||
public class MusicUtil {
|
||||
|
@ -303,7 +302,7 @@ public class MusicUtil {
|
|||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
final int id = cursor.getInt(0);
|
||||
final Song song = SongLoader.INSTANCE.getSong(activity, id).blockingFirst();
|
||||
final Song song = SongLoader.INSTANCE.getSong(activity, id);
|
||||
MusicPlayerRemote.INSTANCE.removeFromQueue(song);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
|
@ -404,9 +403,9 @@ public class MusicUtil {
|
|||
|
||||
public static void toggleFavorite(@NonNull final Context context, @NonNull final Song song) {
|
||||
if (isFavorite(context, song)) {
|
||||
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).blockingFirst().id);
|
||||
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).id);
|
||||
} else {
|
||||
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).blockingFirst().id,
|
||||
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).id,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
@ -416,18 +415,18 @@ public class MusicUtil {
|
|||
return playlist.name != null && playlist.name.equals(context.getString(R.string.favorites));
|
||||
}
|
||||
|
||||
private static Observable<Playlist> getFavoritesPlaylist(@NonNull final Context context) {
|
||||
private static Playlist getFavoritesPlaylist(@NonNull final Context context) {
|
||||
return PlaylistLoader.INSTANCE.getPlaylist(context, context.getString(R.string.favorites));
|
||||
}
|
||||
|
||||
private static Observable<Playlist> getOrCreateFavoritesPlaylist(@NonNull final Context context) {
|
||||
private static Playlist getOrCreateFavoritesPlaylist(@NonNull final Context context) {
|
||||
return PlaylistLoader.INSTANCE.getPlaylist(context,
|
||||
PlaylistsUtil.createPlaylist(context, context.getString(R.string.favorites)));
|
||||
}
|
||||
|
||||
public static boolean isFavorite(@NonNull final Context context, @NonNull final Song song) {
|
||||
return PlaylistsUtil
|
||||
.doPlaylistContains(context, getFavoritesPlaylist(context).blockingFirst().id, song.getId());
|
||||
.doPlaylistContains(context, getFavoritesPlaylist(context).id, song.getId());
|
||||
}
|
||||
|
||||
public static boolean isArtistNameUnknown(@Nullable String artistName) {
|
||||
|
@ -475,6 +474,15 @@ public class MusicUtil {
|
|||
return duration;
|
||||
}
|
||||
|
||||
public static int indexOfSongInList(List<Song> songs, int songId) {
|
||||
for (int i = 0; i < songs.size(); i++) {
|
||||
if (songs.get(i).getId() == songId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getYearString(int year) {
|
||||
return year > 0 ? String.valueOf(year) : "-";
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* 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()
|
|
@ -38,7 +38,6 @@ 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 io.reactivex.Observable;
|
||||
|
||||
import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
||||
|
||||
|
@ -262,7 +261,9 @@ public class PlaylistsUtil {
|
|||
return "";
|
||||
}
|
||||
|
||||
public static Observable<File> savePlaylist(Context context, Playlist playlist) throws IOException {
|
||||
@Nullable
|
||||
public static File savePlaylist(@NonNull Context context,
|
||||
@NonNull Playlist playlist) throws IOException {
|
||||
return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,10 +61,8 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import code.name.monkey.appthemehelper.ThemeStore;
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil;
|
||||
import code.name.monkey.appthemehelper.util.TintHelper;
|
||||
import code.name.monkey.retromusic.App;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
public class RetroUtil {
|
||||
|
||||
|
@ -176,6 +174,7 @@ public class RetroUtil {
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
|
||||
@Nullable Resources.Theme theme) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
|
@ -184,11 +183,11 @@ public class RetroUtil {
|
|||
return VectorDrawableCompat.create(res, resId, theme);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id,
|
||||
@ColorInt int color) {
|
||||
return TintHelper
|
||||
.createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()),
|
||||
color);
|
||||
return TintHelper.createTintedDrawable(
|
||||
getVectorDrawable(context.getResources(), id, context.getTheme()), color);
|
||||
}
|
||||
|
||||
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int id,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue