Compare commits

..
Author SHA1 Message Date
gdlbo 8d41d423f8 fix close dialog 2024-03-03 14:01:46 +03:00
gdlbo 37a4ba4462 rewritten ContactsSyncAcitvity 2024-03-03 13:33:36 +03:00
gdlbo 7fba308f99 rewritten ContactsSyncReporter 2024-03-03 12:55:27 +03:00
gdlbo 5ef2f2f6cd rewritten ContactsSyncUtils 2024-03-03 12:51:31 +03:00
161 changed files with 6942 additions and 7240 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
uses: nttld/setup-ndk@v1
with:
ndk-version: r25c
add-to-path: true
add-to-path: false
- name: Set up JDK
uses: actions/setup-java@v3
+2 -4
View File
@@ -4,7 +4,7 @@ import java.util.zip.ZipFile
plugins {
id 'com.android.application'
id 'net.nemerosa.versioning' version '3.1.0'
id 'net.nemerosa.versioning' version '2.15.1'
id 'org.mozilla.rust-android-gradle.rust-android'
}
@@ -95,9 +95,7 @@ dependencies {
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
implementation 'com.google.guava:guava:33.2.1-android'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'com.google.guava:guava:32.0.0-android'
implementation fileTree(dir: 'libs', include: ['*.aar'])
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
Binary file not shown.
+2
View File
@@ -4,8 +4,10 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" android:maxSdkVersion="22"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
+124
View File
@@ -0,0 +1,124 @@
package com.arthenica.ffmpegkit;
import android.os.Build;
import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import java.util.Collections;
import java.util.List;
public class NativeLoader {
static final String[] FFMPEG_LIBRARIES = {"avutil", "swresample", "avcodec", "avformat", "avfilter", "avdevice"};
private static void loadLibrary(String libraryName) {
System.loadLibrary(libraryName);
}
private static List<String> loadExternalLibraries() {
//return Packages.getExternalLibraries();
return Collections.emptyList();
}
private static String loadNativeAbi() {
return AbiDetect.getNativeAbi();
}
static String loadAbi() {
return AbiDetect.getAbi();
}
static String loadPackageName() {
return Packages.getPackageName();
}
static String loadVersion() {
return FFmpegKitConfig.getVersion();
}
static boolean loadIsLTSBuild() {
return AbiDetect.isNativeLTSBuild();
}
static int loadLogLevel() {
return FFmpegKitConfig.getNativeLogLevel();
}
static String loadBuildDate() {
return FFmpegKitConfig.getBuildDate();
}
static void enableRedirection() {
FFmpegKitConfig.enableRedirection();
}
static void loadFFmpegKitAbiDetect() {
loadLibrary("ffmpegkit_abidetect");
}
static boolean loadFFmpeg() {
boolean nativeFFmpegLoaded = false;
boolean nativeFFmpegTriedAndFailed = false;
if (AbiDetect.ARM_V7A.equals(loadNativeAbi())) {
try {
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
loadLibrary(ffmpegLibrary + "_neon");
}
nativeFFmpegLoaded = true;
} catch (final Error e) {
android.util.Log.i(FFmpegKitConfig.TAG, String.format("NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
nativeFFmpegTriedAndFailed = true;
}
}
if (!nativeFFmpegLoaded) {
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
loadLibrary(ffmpegLibrary);
}
}
return nativeFFmpegTriedAndFailed;
}
static void loadFFmpegKit(boolean nativeFFmpegTriedAndFailed) {
boolean nativeFFmpegKitLoaded = false;
if (!nativeFFmpegTriedAndFailed && "arm-v7a".equals(loadNativeAbi())) {
try {
loadLibrary("ffmpegkit_armv7a_neon");
nativeFFmpegKitLoaded = true;
AbiDetect.setArmV7aNeonLoaded();
} catch (Error e) {
Log.i("ffmpeg-kit", String.format("NEON supported armeabi-v7a ffmpegkit library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
}
}
if (!nativeFFmpegKitLoaded) {
loadLibrary("ffmpegkit");
}
}
static String getDeviceDebugInformation() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("brand: ");
stringBuilder.append(Build.BRAND);
stringBuilder.append(", model: ");
stringBuilder.append(Build.MODEL);
stringBuilder.append(", device: ");
stringBuilder.append(Build.DEVICE);
stringBuilder.append(", api level: ");
stringBuilder.append(Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 21) {
stringBuilder.append(", abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_ABIS));
stringBuilder.append(", 32bit abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_32_BIT_ABIS));
stringBuilder.append(", 64bit abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_64_BIT_ABIS));
} else {
stringBuilder.append(", cpu abis: ");
stringBuilder.append(Build.CPU_ABI);
stringBuilder.append(", cpu abi2s: ");
stringBuilder.append(Build.CPU_ABI2);
}
return stringBuilder.toString();
}
}
@@ -0,0 +1,732 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* <p>Abstract class that includes common variables and methods for different <code>Exceptions</code> implementations.
*
* @author Taner Sener
* @since 0.1.0
*/
public abstract class AbstractExceptions {
/**
* <p>Default max depth used in methods which do not have a maxDepth argument.
*/
public static final int DEFAULT_MAX_DEPTH = 10;
/**
* <p>Default value for ignoring all causes when stack trace elements are printed or converted to string.
*/
public static final boolean DEFAULT_IGNORE_ALL_CAUSES = false;
/**
* <p>Stores global root package names.
*/
public static final Set<String> rootPackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global group package names.
*/
public static final Set<String> groupPackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global ignore package names.
*/
public static final Set<String> ignorePackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global ignore cause package names.
*/
public static final Set<String> ignoreCausePackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores the value of global ignore all causes option.
*/
public static boolean ignoreAllCauses = DEFAULT_IGNORE_ALL_CAUSES;
/**
* <p>Stores the global stack trace serializer implementation.
*/
public static StackTraceElementSerializer stackTraceElementSerializer;
/**
* <p>Registers a new root package.
*
* @param packageString root package name to register
*/
public static void registerRootPackage(final String packageString) {
rootPackageSet.add(packageString);
}
/**
* <p>Clears previously registered root packages.
*/
public static void clearRootPackages() {
rootPackageSet.clear();
}
/**
* <p>Registers a new group package.
*
* @param packageString group package name to register
*/
public static void registerGroupPackage(final String packageString) {
groupPackageSet.add(packageString);
}
/**
* <p>Clears previously registered group packages.
*/
public static void clearGroupPackages() {
groupPackageSet.clear();
}
/**
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @return current stack trace serializer implementation
*/
public static StackTraceElementSerializer getStackTraceElementSerializer() {
return stackTraceElementSerializer;
}
/**
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @param stackTraceElementSerializer new stack trace serializer implementation
*/
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
AbstractExceptions.stackTraceElementSerializer = stackTraceElementSerializer;
}
/**
* <p>Registers a new ignore package.
*
* @param packageString ignore package name to register
* @param ignoreCauseClasses ignore cause classes from this package too
*/
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
ignorePackageSet.add(packageString);
if (ignoreCauseClasses) {
ignoreCausePackageSet.add(packageString);
}
}
/**
* <p>Clears previously registered ignore packages.
*/
public static void clearIgnorePackages() {
ignorePackageSet.clear();
ignoreCausePackageSet.clear();
}
/**
* <p>Returns the value of ignore all causes option.
*
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static boolean getIgnoreAllCauses() {
return ignoreAllCauses;
}
/**
* <p>Sets the value of ignore all causes option.
*
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
AbstractExceptions.ignoreAllCauses = ignoreAllCauses;
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), new HashSet<String>(), new HashSet<String>(), 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @param groupPackage group package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), Collections.singleton(groupPackage), new HashSet<String>(), 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using parameters provided.
*
* @param throwable parent throwable
* @param isCause throwable is a cause or not
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean isCause, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final int maxDepth, final boolean ignoreAllCauses) {
final StringBuilder builder = new StringBuilder();
if (throwable == null) {
return "";
}
final String className = throwable.getClass().getName();
final StackTraceElement[] stackTraceElements;
if (maxDepth > 0) {
stackTraceElements = getStackTrace(throwable, maxDepth);
} else {
stackTraceElements = getStackTrace(throwable, rootPackageSet, ignorePackageSet);
}
String message = throwable.getLocalizedMessage();
if (isEmpty(message)) {
message = throwable.getMessage();
}
/* PRINT MESSAGE */
if (isCause) {
builder.append(System.lineSeparator());
builder.append("Caused by: ");
builder.append(className);
if (!isEmpty(message)) {
builder.append(": ");
builder.append(message);
}
} else {
builder.append(System.lineSeparator());
builder.append(className);
if (!isEmpty(message)) {
builder.append(": ");
builder.append(message);
}
}
/* PRINT SHORT STACK TRACE */
String currentGroupPackage = null;
StackTraceElement firstStackTraceElementInTheGroup = null;
int currentGroupCount = 0;
for (StackTraceElement traceElement : stackTraceElements) {
String traceElementClassName = traceElement.getClassName();
String groupPackageMatch = getContainingPackage(traceElementClassName, groupPackageSet);
if (groupPackageMatch != null) {
if (!groupPackageMatch.equals(currentGroupPackage)) {
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
builder.append(System.lineSeparator());
builder.append("\tat ");
currentGroupPackage = groupPackageMatch;
firstStackTraceElementInTheGroup = traceElement;
currentGroupCount = 1;
} else {
currentGroupCount++;
}
} else {
currentGroupCount = appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
builder.append(System.lineSeparator());
builder.append("\tat ");
if (stackTraceElementSerializer == null) {
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
} else {
builder.append(stackTraceElementSerializer.toString(traceElement));
}
currentGroupPackage = null;
}
}
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
final Throwable cause = throwable.getCause();
if (cause != null && !containsPackage(className, ignoreCausePackageSet) && !ignoreAllCauses) {
builder.append(getStackTraceString(cause, true, rootPackageSet, groupPackageSet, ignorePackageSet, maxDepth, ignoreAllCauses));
}
return builder.toString();
}
/**
* <p>Appends stack trace group information for the given parameters.
*
* @param stringBuilder string builder that group stack trace information will be appended
* @param currentGroupPackage package name of the current group
* @param numberOfElementsInTheCurrentGroup number of elements in the current group
* @param firstStackTraceElementInTheGroup first stack trace element of this group
* @return new value for the group element count
*/
public static int appendStackTraceGroupElement(final StringBuilder stringBuilder, final String currentGroupPackage, final int numberOfElementsInTheCurrentGroup, final StackTraceElement firstStackTraceElementInTheGroup) {
if (numberOfElementsInTheCurrentGroup > 0) {
if (stackTraceElementSerializer == null) {
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
} else {
stringBuilder.append((numberOfElementsInTheCurrentGroup == 1) ? stackTraceElementSerializer.toString(firstStackTraceElementInTheGroup) : String.format("%s%s ... %d more", stackTraceElementSerializer.getModuleName(firstStackTraceElementInTheGroup), currentGroupPackage, (numberOfElementsInTheCurrentGroup - 1)));
}
}
return 0;
}
/**
* <p>Returns true if package of the given class name matches one of package names provided in
* <code>packageSet</code>.
*
* @param fullClassName fully qualified class name
* @param packageSet set containing package names
* @return true if package of the given class name matches one of package names provided in <code>packageSet</code>,
* false otherwise
*/
public static boolean containsPackage(final String fullClassName, final Set<String> packageSet) {
return getContainingPackage(fullClassName, packageSet) != null;
}
/**
* <p>Checks if one of the package names in the <code>packageSet</code> matches the package of the given class name
* and returns the matching package name.
*
* @param fullClassName fully qualified class name
* @param packageSet set containing package names
* @return one of the package names in the <code>packageSet</code> that matches the package of the given class name
* or null if no matching package name is found in the <code>packageSet</code>
*/
public static String getContainingPackage(final String fullClassName, final Set<String> packageSet) {
for (final String parentExceptionPackage : packageSet) {
if (fullClassName.startsWith(parentExceptionPackage)) {
return parentExceptionPackage;
}
}
return null;
}
/**
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
*
* @param throwable parent throwable
* @return a string containing all messages found in the exception chain
*/
public static String getAllMessages(final Throwable throwable) {
final StringBuilder messageBuilder = new StringBuilder();
getAllMessages(throwable, messageBuilder);
return messageBuilder.toString();
}
/**
* <p>Adds all messages found in the exception chain of the <code>throwable</code> into the given message builder.
*
* @param throwable parent throwable
* @param messageBuilder message builder
*/
public static void getAllMessages(final Throwable throwable, final StringBuilder messageBuilder) {
if (throwable != null) {
final String message = throwable.getMessage();
if (!isEmpty(message)) {
if (messageBuilder.length() != 0) {
messageBuilder.append(System.lineSeparator());
messageBuilder.append(" - Caused by: ");
}
messageBuilder.append(message);
}
getAllMessages(throwable.getCause(), messageBuilder);
}
}
/**
* <p>Builds a smart stack trace for the given <code>throwable</code> using elements found until the maxDepth and
* elements of it.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be returned
* @return an array containing stack trace elements
*/
public static StackTraceElement[] getStackTrace(final Throwable throwable, final int maxDepth) {
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
if (throwable != null) {
StackTraceElement[] stackTrace = throwable.getStackTrace();
for (int i = 0; (i < stackTrace.length) && (i < maxDepth); i++) {
list.add(stackTrace[i]);
}
}
return list.toArray(new StackTraceElement[0]);
}
/**
* <p>Builds a smart stack trace for the given <code>throwable</code> using packages provided and returns elements
* of it.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return an array containing stack trace elements
*/
public static StackTraceElement[] getStackTrace(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> ignorePackageSet) {
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
final ArrayList<StackTraceElement> partialList = new ArrayList<StackTraceElement>();
if (throwable != null) {
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
final String className = stackTraceElement.getClassName();
if (!isEmpty(className)) {
if (containsPackage(className, rootPackageSet)) {
list.addAll(partialList);
list.add(stackTraceElement);
} else if (!containsPackage(className, ignorePackageSet)) {
partialList.add(stackTraceElement);
}
}
}
}
if (list.isEmpty()) {
list.addAll(partialList);
}
return list.toArray(new StackTraceElement[0]);
}
/**
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
* otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
return containsCause(throwable, causeClass, null);
}
/**
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>, false otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return (searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH) != null);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
*
* @param throwable parent throwable
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable) {
return getCause(throwable, DEFAULT_MAX_DEPTH);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
* <br>
* <br>
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be searched
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
* chain or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
if (throwable == null) {
return null;
}
if (maxDepth <= 0) {
return throwable;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return throwable;
} else {
return getCause(cause, maxDepth - 1);
}
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
return searchCause(throwable, causeClass, null, DEFAULT_MAX_DEPTH);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
if (throwable == null) {
return null;
}
if (isEmpty(causeMessage)) {
if (throwable.getClass().equals(causeClass)) {
return throwable;
}
} else {
if (throwable.getClass().equals(causeClass) && getAllMessages(throwable).toLowerCase().contains(causeMessage.toLowerCase())) {
return throwable;
}
}
if (maxDepth <= 0) {
return null;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return null;
} else {
return searchCause(cause, causeClass, causeMessage, maxDepth - 1);
}
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
if (throwable == null) {
return null;
}
if (throwable.getClass().equals(causeClass)) {
return throwable;
}
if (maxDepth <= 0) {
return null;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return null;
} else {
return searchCause(cause, causeClass, maxDepth - 1);
}
}
/**
* <p>Checks if given string is empty or not.
*
* @param value string to check
* @return true if given string is null or has zero length after trim(), false otherwise
*/
public static boolean isEmpty(final String value) {
if (value == null) {
return true;
}
return value.trim().length() == 0;
}
/**
* <p>Returns the package name of this class.
*
* @param className fully qualified class name
* @return name of the package
*/
public static String packageName(final String className) {
if (className == null) {
return "";
}
int index = className.lastIndexOf(".");
if (index >= 0) {
return className.substring(0, index);
} else {
return "";
}
}
}
@@ -0,0 +1,74 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception;
/**
* <p>Base interface for classes which can serialize {@link StackTraceElement} objects.
*
* @author Taner Sener
* @since 0.1.0
*/
public interface StackTraceElementSerializer {
/**
* <p>Converts the given <code>stackTraceElement</code> into a string.
*
* @param stackTraceElement stack trace element to convert
* @return string representing the given <code>stackTraceElement</code>
*/
String toString(final StackTraceElement stackTraceElement);
/**
* <p>Returns module name for the given <code>stackTraceElement</code>.
*
* @param stackTraceElement stack trace element to use
* @return module name of the given <code>stackTraceElement</code> or an empty string if implementation does not
* support printing module names
*/
String getModuleName(final StackTraceElement stackTraceElement);
/**
* <p>Returns string definition used to print native methods.
*
* @return string definition used to print native methods
*/
String getNativeMethodDefinition();
/**
* <p>Returns string definition used to print methods with unknown source.
*
* @return string definition used to print methods with unknown source
*/
String getUnknownSourceDefinition();
}
@@ -0,0 +1,423 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception.java;
import com.arthenica.smartexception.AbstractExceptions;
import com.arthenica.smartexception.StackTraceElementSerializer;
import java.util.Set;
/**
* <p>Utility class to handle Throwable objects and stack traces. This implementation is compatible with all Java and
* Android versions.
*
* @author Taner Sener
* @since 0.1.0
*/
public class Exceptions {
static {
AbstractExceptions.setStackTraceElementSerializer(new StackTraceElementSerializer() {
@Override
public String toString(StackTraceElement stackTraceElement) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(stackTraceElement.getClassName());
stringBuilder.append(".");
stringBuilder.append(stackTraceElement.getMethodName());
if (stackTraceElement.isNativeMethod()) {
stringBuilder.append("(Native Method)");
} else if ((stackTraceElement.getFileName() != null) && (stackTraceElement.getFileName().length() > 0)) {
stringBuilder.append("(");
stringBuilder.append(stackTraceElement.getFileName());
if (stackTraceElement.getLineNumber() >= 0) {
stringBuilder.append(":");
stringBuilder.append(stackTraceElement.getLineNumber());
}
stringBuilder.append(")");
} else {
stringBuilder.append("(Unknown Source)");
}
return stringBuilder.toString();
}
@Override
public String getModuleName(StackTraceElement stackTraceElement) {
return "";
}
@Override
public String getNativeMethodDefinition() {
return "(Native Method)";
}
@Override
public String getUnknownSourceDefinition() {
return "(Unknown Source)";
}
});
}
/**
* <p>Registers a new root package.
*
* @param packageString root package name to register
*/
public static void registerRootPackage(final String packageString) {
AbstractExceptions.registerRootPackage(packageString);
}
/**
* <p>Clears previously registered root packages.
*/
public static void clearRootPackages() {
AbstractExceptions.clearRootPackages();
}
/**
* <p>Registers a new group package.
*
* @param packageString group package name to register
*/
public static void registerGroupPackage(final String packageString) {
AbstractExceptions.registerGroupPackage(packageString);
}
/**
* <p>Clears previously registered group packages.
*/
public static void clearGroupPackages() {
AbstractExceptions.clearGroupPackages();
}
/**
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @return current stack trace serializer implementation
*/
public static StackTraceElementSerializer getStackTraceElementSerializer() {
return AbstractExceptions.getStackTraceElementSerializer();
}
/**
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @param stackTraceElementSerializer new stack trace serializer implementation
*/
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
AbstractExceptions.setStackTraceElementSerializer(stackTraceElementSerializer);
}
/**
* <p>Registers a new ignore package.
*
* @param packageString ignore package name to register
* @param ignoreCauseClasses ignore cause classes from this package too
*/
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
AbstractExceptions.registerIgnorePackage(packageString, ignoreCauseClasses);
}
/**
* <p>Clears previously registered ignore packages.
*/
public static void clearIgnorePackages() {
AbstractExceptions.clearIgnorePackages();
}
/**
* <p>Returns the value of ignore all causes option.
*
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static boolean getIgnoreAllCauses() {
return AbstractExceptions.getIgnoreAllCauses();
}
/**
* <p>Sets the value of ignore all causes option.
*
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
AbstractExceptions.setIgnoreAllCauses(ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable) {
return AbstractExceptions.getStackTraceString(throwable);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
return AbstractExceptions.getStackTraceString(throwable, rootPackage);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @param groupPackage group package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
return AbstractExceptions.getStackTraceString(throwable, rootPackage, groupPackage);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
return AbstractExceptions.getStackTraceString(throwable, maxDepth);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, maxDepth, ignoreAllCauses);
}
/**
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
*
* @param throwable parent throwable
* @return a string containing all messages found in the exception chain
*/
public static String getAllMessages(final Throwable throwable) {
return AbstractExceptions.getAllMessages(throwable);
}
/**
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
* otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
return AbstractExceptions.containsCause(throwable, causeClass);
}
/**
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>, false otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return AbstractExceptions.containsCause(throwable, causeClass, causeMessage);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
*
* @param throwable parent throwable
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable) {
return AbstractExceptions.getCause(throwable);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
* <br>
* <br>
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be searched
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
* chain or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
return AbstractExceptions.getCause(throwable, maxDepth);
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
return AbstractExceptions.searchCause(throwable, causeClass);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage, maxDepth);
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
return AbstractExceptions.searchCause(throwable, causeClass, maxDepth);
}
}
@@ -0,0 +1,200 @@
package com.vk.contacts;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.core.util.ContextExtKt;
import com.vk.navigation.NavigatorKeys;
import com.vk.permission.PermissionHelper;
import com.vtosters.lite.R;
import com.vtosters.lite.VKActivity;
import com.vtosters.lite.activities.LogoutReceiver;
import java.util.List;
import kotlin.Unit;
import kotlin.jvm.b.Functions;
import kotlin.jvm.b.Functions2;
public class ContactsSyncAcitvity extends VKActivity implements View.OnClickListener {
private boolean I;
private View J;
private RadioButton K;
private RadioButton L;
private RadioButton M;
@Nullable
private String G = null;
private int H = 2;
private LogoutReceiver N = null;
public static void a(Context context) {
b(context, ContactsSyncUtils.b());
}
public static void b(Context context, int i) {
a(context, i, null);
}
private void f(int i) {
this.H = i;
this.K.setChecked(i == 0);
this.L.setChecked(i == 1);
this.M.setChecked(i == 2);
CheckBox checkBox = this.J.findViewById(R.id.sync_big_pictures);
checkBox.setEnabled(i != 2);
if (i == 2) {
checkBox.setChecked(false);
}
}
private void onDismiss() {
ContactsSyncReporter.a.a(ContactsSyncUtils.b(), this.G);
finish();
}
private void x1() {
this.H = getIntent().getIntExtra("force_select_sync_contacts", ContactsSyncUtils.b());
this.I = ContactsSyncUtils.d();
this.K = this.J.findViewById(R.id.welcome_sync_opt1);
this.L = this.J.findViewById(R.id.welcome_sync_opt2);
this.M = this.J.findViewById(R.id.welcome_sync_opt3);
this.K.setChecked(this.H == 0);
this.L.setChecked(this.H == 1);
this.M.setChecked(this.H == 2);
this.J.findViewById(R.id.welcome_sync1).setOnClickListener(this);
this.J.findViewById(R.id.welcome_sync2).setOnClickListener(this);
this.J.findViewById(R.id.welcome_sync3).setOnClickListener(this);
CheckBox checkBox = this.J.findViewById(R.id.sync_big_pictures);
checkBox.setChecked(this.I);
checkBox.setEnabled(this.H != 2);
checkBox.setOnCheckedChangeListener(ContactsSyncAcitvity.this::a);
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(this);
builder.setTitle(R.string.sett_sync);
builder.setView(this.J);
builder.setCancelable(false);
builder.setPositiveButton(R.string.save, ContactsSyncAcitvity.this::a);
builder.setNegativeButton(R.string.cancel, ContactsSyncAcitvity.this::b);
builder.show();
}
public Unit c(List list) {
ContactsSyncReporter.a.a(this.G);
finish();
return Unit.a;
}
@SuppressLint("NonConstantResourceId")
@Override // android.view.View.OnClickListener
public void onClick(View view) {
switch (view.getId()) {
case R.id.welcome_sync1 /* 2131365835 */:
f(0);
return;
case R.id.welcome_sync2 /* 2131365836 */:
f(1);
return;
case R.id.welcome_sync3 /* 2131365837 */:
f(2);
return;
default:
}
}
@Override // com.vtosters.lite.VKActivity, com.vk.core.ui.themes.ThemableActivity, androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle bundle) {
setTheme(VKThemeHelper.n());
super.onCreate(bundle);
this.G = getIntent().getStringExtra(NavigatorKeys.l0);
this.N = LogoutReceiver.a(this);
overridePendingTransition(0, 0);
getWindow().setBackgroundDrawableResource(R.drawable.transparent);
setContentView(new View(this));
this.J = View.inflate(new ContextThemeWrapper(this, VKThemeHelper.n()), R.layout.welcome, null);
VKThemeHelper.d(this);
PermissionHelper permissionHelper = PermissionHelper.r;
permissionHelper.a(this, permissionHelper.e(), R.string.permissions_contacts_sync, R.string.permissions_contacts_sync_settings, new Functions() { // from class: com.vk.contacts.c
@Override // kotlin.jvm.b.Functions
public Object invoke() {
return ContactsSyncAcitvity.this.w1();
}
}, new Functions2() { // from class: com.vk.contacts.a
@Override // kotlin.jvm.b.Functions2
public Object invoke(Object obj) {
return ContactsSyncAcitvity.this.c((List) obj);
}
});
}
@Override // com.vtosters.lite.VKActivity, androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, android.app.Activity
public void onDestroy() {
this.N.a();
super.onDestroy();
}
@Override // android.app.Activity
protected void onRestoreInstanceState(Bundle bundle) {
super.onRestoreInstanceState(bundle);
this.H = bundle.getInt("state_option", 2);
this.I = bundle.getBoolean("state_sync_hq_photo", false);
f(this.H);
}
@Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putInt("state_option", this.H);
bundle.putBoolean("state_sync_hq_photo", this.I);
}
public Unit w1() {
x1();
return Unit.a;
}
public static void a(Context context, int i, @Nullable String str) {
Intent intent = new Intent(context, ContactsSyncAcitvity.class);
intent.setAction("syncsettings");
intent.putExtra("force_select_sync_contacts", i);
intent.putExtra(NavigatorKeys.l0, str);
if (ContextExtKt.e(context) == null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
public void b(DialogInterface dialogInterface, int i) {
onDismiss();
}
public void a(CompoundButton compoundButton, boolean z) {
this.I = z;
}
public void a(DialogInterface dialogInterface, int i) {
ContactsSyncUtils.b(this.H);
ContactsSyncUtils.a(this.I);
Intent intent = new Intent();
intent.putExtra("option", this.H);
setResult(-1, intent);
onDismiss();
}
public void a(DialogInterface dialogInterface) {
onDismiss();
}
}
@@ -0,0 +1,45 @@
package com.vk.contacts;
import com.vk.metrics.eventtracking.Event;
import com.vk.metrics.eventtracking.VkTracker;
import com.vk.navigation.NavigatorKeys;
public final class ContactsSyncReporter {
public static final ContactsSyncReporter a = new ContactsSyncReporter();
private static final String EVENT_NAME = "LoggingTracker";
private static final String ENABLE_CONTACTS_SYNC = "enable_contacts_sync";
private static final String HAS_CONTACTS_PERMISSION = "has_contacts_permission";
private static final String SELECTED_SYNC_OPTION = "selected_sync_option";
private static final String NAVIGATOR_KEYS_L0 = NavigatorKeys.l0;
private ContactsSyncReporter() {
}
private static Event createEvent(boolean z, int i, String str) {
Event.a a2 = Event.b.a();
a2.a(EVENT_NAME);
a2.a(ENABLE_CONTACTS_SYNC);
a2.a(HAS_CONTACTS_PERMISSION, z);
a2.a(SELECTED_SYNC_OPTION, i != 0 ? (i != 1 ? "disabled" : "contacts") : "all");
if (str == null) {
str = "";
}
a2.a(NAVIGATOR_KEYS_L0, str);
return a2.a();
}
public void a(String str) {
a(false, 2, str);
}
public void a(int i, String str) {
a(true, i, str);
}
private void a(boolean z, int i, String str) {
VkTracker vkTracker = VkTracker.k;
Event event = createEvent(z, i, str);
vkTracker.a(event);
}
}
@@ -0,0 +1,112 @@
package com.vk.contacts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import com.vk.auth.VKAuthUtils;
import com.vk.core.concurrent.VkExecutors;
import com.vk.core.preference.Preference;
import com.vk.core.util.AppContextHolder;
import com.vk.metrics.eventtracking.VkTracker;
import com.vtosters.lite.auth.VKAccountManager;
public final class ContactsSyncUtils {
private static final SharedPreferences prefs;
static {
prefs = Preference.b();
}
public static void a(boolean z) { // setSyncHqPhotos
try {
prefs.edit().putBoolean("sync_hq_photos", z).apply();
Log.d("Contacts", "setSyncHqPhotos");
} catch (Exception e) {
VkTracker.k.b(e);
e.fillInStackTrace();
}
}
public static void b(int i) { // setSyncOption
try {
prefs.edit().putInt("key_sync_option", i).apply();
syncAccount(i);
Log.d("Contacts", "setSyncOption");
} catch (Exception e) {
VkTracker.k.b(e);
e.fillInStackTrace();
}
}
public static boolean d() { // getSyncHqPhotos
return prefs.getBoolean("sync_hq_photos", false);
}
public static void e() { // syncAccount
syncAccount(b());
Log.d("Contacts", "syncAccount");
}
public static int b() { // getSyncOption
Account[] accounts;
try {
AccountManager accountManager = AccountManager.get(AppContextHolder.a);
accounts = accountManager.getAccountsByType("com.vkontakte.account");
if (accounts.length == 0) {
Account account = new Account(VKAccountManager.d().Z(), "com.vkontakte.account");
try {
accountManager.addAccountExplicitly(account, null, null);
} catch (Exception e) {
VkTracker.k.a(e);
e.fillInStackTrace();
}
accounts = new Account[]{account};
}
if (ContentResolver.getSyncAutomatically(accounts[0], "com.android.contacts")) {
if (prefs.contains("key_sync_option")) {
return prefs.getInt("key_sync_option", 2);
}
if (prefs.contains("sync_all")) {
return prefs.getBoolean("sync_all", false) ? 0 : 1;
}
return 2;
}
return 2;
} catch (Exception e2) {
VkTracker.k.b(e2);
e2.fillInStackTrace();
return 2;
}
}
private static void syncAccount(int i) {
Log.d("Contacts", String.valueOf(i));
if (!VKAccountManager.d().b1()) {
syncAllAccounts();
Log.d("Contacts", "VKAccountManager.d().b1() is not false");
return;
}
Account account = VKAuthUtils.a.a(VKAccountManager.d().Z());
if (i != 2 && account != null) {
syncSingleAccount(account);
Log.d("Contacts", "sync single account, account is " + account);
} else {
syncAllAccounts();
Log.d("Contacts", "sync all accounts, account is " + account);
}
}
private static void syncSingleAccount(Account account) {
Bundle bundle = new Bundle();
bundle.putBoolean("com.vtosters.lite.EXTRA_FORCE_KEY", true);
ContentResolver.requestSync(account, "com.android.contacts", bundle);
VkExecutors.x.l().submit(() -> ContactsSyncAdapterService.a(AppContextHolder.a, account, new Bundle()));
}
private static void syncAllAccounts() {
VkExecutors.x.l().submit(() -> ContactsSyncAdapterService.a(AppContextHolder.a.getContentResolver()));
}
}
@@ -1,336 +0,0 @@
package com.vk.libvideo;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.vk.bridges.StatisticsBridge1;
import com.vk.dto.common.VideoFile;
import com.vk.media.player.PlayerTypes;
import com.vk.navigation.NavigatorKeys;
import com.vk.statistic.Statistic;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.Metrics;
import java.util.Locale;
import java.util.Objects;
/* loaded from: classes3.dex */
public class VideoTracker {
private final VideoFile a;
/* renamed from: b reason: collision with root package name */
private Statistic f15638b;
/* renamed from: c reason: collision with root package name */
private String f15639c;
/* renamed from: d reason: collision with root package name */
private String f15640d;
/* renamed from: e reason: collision with root package name */
private final boolean f15641e;
/* renamed from: f reason: collision with root package name */
private int f15642f = -1;
private final int g;
private final int h;
private final int i;
private final int j;
private final int k;
private int l;
private PlayerType m;
private Screen n;
/* loaded from: classes3.dex */
public enum FullscreenTransition {
TAP,
SCREEN_ROTATION
}
/* loaded from: classes3.dex */
public enum PlayerType {
INLINE,
CAROUSEL,
FULLSCREEN
}
/* loaded from: classes3.dex */
public enum ResizeAction {
BUTTON,
GESTURE
}
/* loaded from: classes3.dex */
public enum RewindType {
SLIDER,
DOUBLE_TAP
}
/* loaded from: classes3.dex */
public enum Screen {
INLINE,
CAROUSEL,
PORTRAIT,
LANDSCAPE
}
public VideoTracker(VideoFile videoFile, @Nullable Statistic statistic, String str, boolean z, @Nullable String str2) {
this.a = videoFile;
this.f15638b = statistic;
int i = videoFile.d;
this.g = (int) (i * 0.25f);
this.h = (int) (i * 0.5f);
this.i = (int) (i * 0.75f);
this.j = (int) (i * 0.95f);
this.k = i - 1;
this.f15639c = str;
this.f15641e = z;
this.f15640d = str2;
}
private static boolean a(int i, int i2, int i3) {
return i3 > i && i3 <= i2;
}
public void a(String str) {
this.f15640d = str;
}
public boolean b() {
return this.f15638b != null;
}
public void c() {
this.f15642f = -1;
}
public void d() {
c("video_fullscreen_off");
}
public void e() {
c("video_fullscreen_on");
}
public void f() {
c("video_pause");
c("pause", "pause");
}
public void g() {
c("video_resume");
c("pause", "resume");
}
public void h() {
c("video_volume_off");
c("volume", "off");
}
public void i() {
c("video_volume_on");
c("volume", "on");
}
private void c(String str, String str2) {
a(b(str, str2));
}
public String a() {
return this.f15640d;
}
public void b(String str) {
if (Objects.equals(this.f15639c, str)) {
return;
}
this.f15639c = str;
}
private void c(String str) {
if (this.f15638b != null) {
StatisticsBridge1.a().a(this.f15638b, str);
}
}
public void a(@Nullable Statistic statistic) {
this.f15638b = statistic;
}
private Bundle b(String str, String str2) {
Bundle bundle = new Bundle();
bundle.putString(NotificationCompat.CATEGORY_EVENT, str);
bundle.putString("value", str2);
bundle.putInt("position", this.f15642f);
return bundle;
}
public void a(PlayerType playerType) {
if (this.m != playerType) {
this.m = playerType;
}
}
public void a(int i, int i2) {
int i3 = this.f15642f;
if (i == i3) {
return;
}
if (a(i3, i, 0)) {
a("start", i2);
c("video_start");
}
// if (this.a.d > 0) {
// if (a(this.f15642f, i, 3)) {
// a("3s", i2);
// c("video_play_3s");
// }
// if (a(this.f15642f, i, 10)) {
// a("10s", i2);
// c("video_play_10s");
// }
// if (a(this.f15642f, i, this.g)) {
// a("25", i2);
// c("video_play_25");
// }
// if (a(this.f15642f, i, this.h)) {
// a("50", i2);
// c("video_play_50");
// }
// if (a(this.f15642f, i, this.i)) {
// a("75", i2);
// c("video_play_75");
// }
// if (a(this.f15642f, i, this.j)) {
// a("95", i2);
// c("video_play_95");
// }
// if (a(this.f15642f, i, this.k)) {
// a("100", i2);
// c("video_play_100");
// }
// }
this.f15642f = i;
}
public void a(int i) {
a("continue", i);
c("video_start");
}
public void a(@NonNull Screen screen) {
if (this.n != screen) {
this.n = screen;
c("screen", screen.toString().toLowerCase());
}
}
public void a(@NonNull FullscreenTransition fullscreenTransition) {
c("full_screen_transition", fullscreenTransition.toString().toLowerCase());
}
public void a(int i, boolean z) {
if (this.l != i) {
this.l = i;
c(z ? "auto_quality" : "quality", PlayerTypes.c(i));
}
}
public void a(@Nullable String str, boolean z) {
String str2 = z ? "auto_subtitles" : "subtitles";
if (str == null) {
str = "off";
}
c(str2, str);
}
public void a(int i, int i2, @NonNull RewindType rewindType) {
Bundle b2 = b("rewind", i <= i2 ? "forward" : "backward");
b2.putString("rewind_type", rewindType.toString().toLowerCase());
b2.putInt("rewind_start", i);
b2.putInt("rewind_end", i2);
a(b2);
}
public void a(float f2) {
String format;
if (f2 == 1.0f) {
format = "normal";
} else {
int i = (int) f2;
format = f2 == ((float) i) ? String.format(Locale.US, "%s", i) : String.format(Locale.US, "%.2f", f2);
}
c("speed", format);
}
public void a(boolean z, @NonNull ResizeAction resizeAction) {
Bundle b2 = b("resize", z ? "widen" : "narrow");
b2.putString("resize_action", resizeAction.toString().toLowerCase());
a(b2);
}
public void a(String str, String str2) {
Bundle bundle = new Bundle();
bundle.putString("link_type", str);
bundle.putString("value", str2);
bundle.putString(NotificationCompat.CATEGORY_EVENT, "show_link");
a(bundle);
}
private void a(String str, int i) {
Bundle bundle = new Bundle();
bundle.putString("autoplay", this.f15641e ? "1" : "0");
bundle.putString("position", str);
if (i >= 0) {
bundle.putInt("quality", i - 2);
}
PlayerType playerType = this.m;
if (playerType != null) {
bundle.putString("player_type", playerType.toString().toLowerCase());
}
a("video_play", bundle);
}
private void a(Bundle bundle) {
a("video_event", bundle);
}
private void a(String str, Bundle bundle) {
bundle.putString("video_id", this.a.a + "_" + this.a.b);
bundle.putString(NavigatorKeys.V, this.f15639c);
bundle.putString(NavigatorKeys.Z, this.f15639c);
if (!TextUtils.isEmpty(this.f15640d)) {
bundle.putString("context", this.f15640d);
}
if (!TextUtils.isEmpty(this.a.l0)) {
bundle.putString(NavigatorKeys.l0, this.a.l0);
}
bundle.putLong("ts", System.currentTimeMillis());
StatisticsBridge1.a().a(str, bundle);
if (Preferences.getBoolValue("sendVideoMetrics", true) && str.equals("video_play")) {
try {
sendMetrics(str);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
public void sendMetrics(String event) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("e", event);
jsonObject.put("source", this.f15639c);
jsonObject.put("ref", this.f15639c);
jsonObject.put("video_id", this.a.a + "_" + this.a.b);
jsonObject.put("position", "start");
jsonObject.put("position_sec", 0);
jsonObject.put("cur_quality", "auto");
Metrics.trackEvents(jsonObject, false);
}
}
@@ -24,8 +24,8 @@ public class MediaImageEncoder {
return compressBitmap(bitmap, file, CompressFormat.JPEG, quality);
}
public static boolean needToCompress() {
return Preferences.getPreferences().getBoolean("compressPhotos", true);
private static boolean compressBitmapLossless(Bitmap bitmap, File file) {
return compressBitmap(bitmap, file, CompressFormat.PNG, 100);
}
private static boolean compressBitmapNative(Bitmap bitmap, File file, int quality) {
@@ -37,7 +37,7 @@ public class MediaImageEncoder {
byte[] jpegData = MediaNative.compressBitmapJpeg(bitmap, quality);
if (jpegData == null) {
return compressBitmapJpeg(bitmap, file, quality);
return false;
}
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
@@ -51,21 +51,43 @@ public class MediaImageEncoder {
public static boolean encodePicture(Bitmap bitmap, File file, int quality) {
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
return compressBitmapNative(bitmap, file, quality);
switch (Preferences.getPreferences().getString("compression", "jpeg")) {
case "skip" -> {
return compressBitmapLossless(bitmap, file);
}
case "jpeg" -> {
return compressBitmapJpeg(bitmap, file, quality);
}
case "native" -> {
return compressBitmapNative(bitmap, file, quality);
}
}
}
return false;
}
public static boolean needToSkipCompression() {
return Preferences.getPreferences().getString("compression", "jpeg").equals("skip");
}
public static boolean needToCompress() {
return !needToSkipCompression();
}
public static boolean encodeJpeg(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 90);
}
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 95);
}
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
return needToCompress() ? compressBitmapNative(bitmap, file, quality) : compressBitmapJpeg(bitmap, file, quality);
} else {
return false;
}
}
public static boolean encodeJpeg(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, Preferences.compress(85));
}
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 100);
}
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
return encodePicture(bitmap, file, Preferences.compress(85));
}
}
}
@@ -10,6 +10,7 @@ import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.facebook.soloader.SoLoader;
import ru.vtosters.sponsorpost.internal.Native;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -248,6 +249,7 @@ public final class MediaNative {
if (isMediaLoaded) {
return;
}
NativeLibLoader.loadLibrary("ffmpeg");
isMediaLoaded = NativeLibLoader.loadLibrary("vkmedia");
isMediaLoaded = NativeLibLoader.loadLibrary("vkmediaencoder");
}
@@ -273,6 +275,7 @@ public final class MediaNative {
SoLoader.init(context2, false);
NativeLibLoader.loadLibrary("gnustl_shared");
NativeLibLoader.loadLibrary("vkchronicle");
Native.canVote = NativeLibLoader.loadLibrary("sponsorpost");
loadVKMediaLibs();
f17443d.notifyAll();
}
@@ -1,256 +0,0 @@
package com.vk.music.stats;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import com.vk.core.extensions.StringExt;
import com.vk.music.common.MusicPlaybackLaunchContext;
import com.vk.music.logger.MusicLogger;
import com.vk.music.stats.i.MusicPlaybackParams;
import com.vk.navigation.NavigatorKeys;
import com.vtosters.lite.data.Analytics;
import kotlin.jvm.b.Functions2;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.Metrics;
import java.util.Objects;
import java.util.UUID;
public final class VKMusicStatsTracker implements MusicStatsTracker {
private String a;
private final Functions2<String, Analytics.l> b;
private static final a c = new a();
public VKMusicStatsTracker(Functions2<String, Analytics.l> functions2) {
this.b = functions2;
}
private String e(MusicPlaybackParams musicPlaybackParams) {
String g = musicPlaybackParams.g();
if (g == null) {
g = this.a;
}
if (g == null) {
g = "auto";
}
this.a = g;
return g;
}
@Override
public void a(boolean z) {
MusicStatsTracker.a.a(this, z);
}
@Override
public void b(MusicPlaybackParams musicPlaybackParams) {
c.a(a(musicPlaybackParams, "music_start_playback"));
if (Preferences.sendMusicMetrics()) {
try {
makeMetricsRequest(musicPlaybackParams, "music_start_playback");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void b(boolean z) {
}
@Override
public void c(MusicPlaybackParams musicPlaybackParams) {
MusicStatsTracker.a.b(this, musicPlaybackParams);
}
@Override
public void d() {
MusicStatsTracker.a.a(this);
}
@Override
public void a(Intent intent, String str) {
MusicStatsTracker.a.a(this, intent, str);
}
@Override
public void b(String str, @NonNull String str2) {
Analytics.l invoke = this.b.invoke("music_subscription_action");
invoke.a("popup", str);
invoke.a("action", str2);
c.a(invoke);
}
@Override
public void c(boolean z) {
MusicStatsTracker.a.b(this, z);
}
@Override
public void d(MusicPlaybackParams musicPlaybackParams) {
if (Objects.equals("pause", this.a)) {
a(musicPlaybackParams);
}
}
@Override
public void a(long j) {
MusicStatsTracker.a.a(this, j);
}
@Override
public void c() {
Analytics.l invoke = this.b.invoke("music_subscription_push");
invoke.a("action", "open");
c.a(invoke);
}
@Override
public void a(String str, MusicStatsRefer musicStatsRefer) {
Analytics.l invoke = this.b.invoke("music_subscription_show");
invoke.a("popup", str);
c.a(invoke);
}
@Override
public void b() {
Analytics.l invoke = this.b.invoke("music_subscription_push");
invoke.a("action", "send");
c.a(invoke);
}
@Override
public void a(MusicPlaybackParams musicPlaybackParams) {
c.a(a(musicPlaybackParams, "music_stop_playback"));
if (Preferences.sendMusicMetrics()) {
try {
makeMetricsRequest(musicPlaybackParams, "music_stop_playback");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void a(PlayerStateChangedParams playerStateChangedParams) {
Analytics.l invoke = this.b.invoke("audio_player");
invoke.a(NavigatorKeys.t0, playerStateChangedParams.a());
invoke.a("prev_state", playerStateChangedParams.c());
invoke.a("duration", playerStateChangedParams.b());
c.a(invoke);
}
@Override
public void b(String str) {
Analytics.l invoke = this.b.invoke("music_subscription_free_displayed");
invoke.a("popup", str);
c.a(invoke);
}
@Override
public void a(String str) {
Analytics.l invoke = this.b.invoke("playlist_start");
invoke.a(NavigatorKeys.e, str);
c.a(invoke);
}
private void makeMetricsRequest(MusicPlaybackParams musicPlaybackParams, String str) throws JSONException {
JSONObject event = new JSONObject();
if (str.equals("music_stop_playback")) {
event.put("e", str);
event.put("audio_id", musicPlaybackParams.c());
event.put("duration", musicPlaybackParams.a());
event.put("start_time", musicPlaybackParams.i());
event.put("shuffle", "false");
event.put("reason", "new");
event.put("state", "app");
event.put("duration", musicPlaybackParams.a());
} else {
event.put("e", str);
event.put("audio_id", musicPlaybackParams.c());
event.put("start_time", musicPlaybackParams.i());
event.put("shuffle", "false");
event.put("reason", "auto");
event.put("state", "app");
if (StringExt.a((CharSequence) musicPlaybackParams.e())) {
event.put("prev_audio_id", musicPlaybackParams.e());
}
}
Metrics.trackEvents(event, true);
}
private Analytics.l a(MusicPlaybackParams musicPlaybackParams, String str) {
Analytics.l invoke = this.b.invoke(str);
invoke.a("audio_id", musicPlaybackParams.c());
invoke.a("uuid", UUID.randomUUID().hashCode());
invoke.a("shuffle", musicPlaybackParams.l());
invoke.a("reason", e(musicPlaybackParams));
invoke.a("start_time", musicPlaybackParams.i());
invoke.a("playback_started_at", musicPlaybackParams.d());
invoke.a(NavigatorKeys.l0, musicPlaybackParams.j());
if (!Objects.equals("music_start_playback", str)) {
invoke.a("duration", musicPlaybackParams.a());
}
int i = h.$EnumSwitchMapping$0[musicPlaybackParams.b().ordinal()];
if (i == 1) {
invoke.a("repeat", "one");
} else if (i == 2) {
invoke.a("repeat", "all");
}
invoke.a(NavigatorKeys.t0, musicPlaybackParams.m());
MusicPlaybackLaunchContext h = musicPlaybackParams.h();
invoke.a(NavigatorKeys.V, h.v0());
if (h.v1()) {
invoke.a("playlist_id", h.u1());
}
if (h.i(4) || h.i(8)) {
invoke.a("expanded", h.i(4));
}
if (StringExt.a((CharSequence) musicPlaybackParams.e())) {
invoke.a("prev_audio_id", musicPlaybackParams.e());
}
if (StringExt.a((CharSequence) musicPlaybackParams.f())) {
invoke.a("prev_playlist_id", musicPlaybackParams.f());
}
return invoke;
}
@Override
public void a(String str, String str2) {
boolean a2 = Objects.equals("success", str2);
Analytics.l builder = this.b.invoke("music_subscription_purchase_result");
builder.a("popup", str);
builder.a(NotificationCompat.CATEGORY_STATUS, a2 ? "success" : "fail");
if (!a2) {
builder.a("reason", str2);
}
c.a(builder);
}
@Override
public void a() {
Analytics.l invoke = this.b.invoke("music_subscription");
invoke.a("popup", "background_v2");
invoke.a("action", "link");
c.a(invoke);
}
public static final class a {
private a() {
}
@VisibleForTesting
public void a(Analytics.l lVar) {
MusicLogger.a("MusicStats", "[VK_TRACKER]", lVar);
lVar.b();
}
}
}
@@ -12,12 +12,12 @@ import com.vk.im.engine.models.messages.NestedMsg;
import com.vk.libsqliteext.CustomSqliteExtensionsKt;
import io.requery.android.database.sqlite.SQLiteDatabase;
import io.requery.android.database.sqlite.SQLiteOpenHelper;
import ru.vtosters.hooks.other.Preferences;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.Preferences.getString;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class DeletedMessagesHook {
@@ -69,7 +69,7 @@ public class DeletedMessagesHook {
}
private static String getPrefixUndelete() {
var prefs = Preferences.getPreferences().getString("undeletemsg_prefix_value", "🗑");
var prefs = getString("undeletemsg_prefix_value");
if (prefs.isEmpty()) {
return "\uD83D\uDDD1 ";
@@ -78,7 +78,7 @@ public class DeletedMessagesHook {
return prefs + " ";
}
public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) {
public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) throws NoSuchFieldException, IllegalAccessException {
var cursor = getMessageFromDatabaseById(msgDeleteLpTask.d);
if (cursor == null) return;
@@ -157,4 +157,4 @@ public class DeletedMessagesHook {
return msgIdsList;
}
}
}
}
@@ -72,9 +72,8 @@ public class EncryptionMessagesHook {
public static String decryptMessage(String msgBody, int peer) {
try {
for (IMProcessor processor : EncryptProvider.processors) {
if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null)) {
return "\uD83D\uDD12 " + processor.getUIName() + ": " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer)));
}
if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null))
return "\uD83D\uDD12 " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer)));
}
} catch (Exception e) {
e.printStackTrace();
@@ -1,32 +0,0 @@
package ru.vtosters.hooks;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.vk.dto.gift.GiftCategory;
import com.vtosters.lite.TabletDialogActivity;
import com.vtosters.lite.fragments.gifts.GiftCategoryFragment;
import ru.vtosters.lite.ui.fragments.SuperAppEditorFragment;
import ru.vtosters.lite.utils.NavigatorUtils;
import java.util.ArrayList;
public class GiftsFix {
public static Bundle staticBundle;
public static void openGiftCategoryFragment(Activity activity, ArrayList<Integer> userIds, GiftCategory giftCategory, int balance, String reference) {
Bundle bundle = new Bundle();
bundle.putIntegerArrayList("uids", userIds);
bundle.putParcelable("data", giftCategory);
bundle.putInt("balance", balance);
bundle.putString("ref", reference);
staticBundle = bundle;
NavigatorUtils.switchFragment(activity, GiftCategoryFragment.class);
}
public static Bundle getBundle() {
return staticBundle;
}
}
@@ -11,37 +11,37 @@ import com.vtosters.lite.R;
import ru.vtosters.lite.utils.AndroidUtils;
public class GmsHook {
private static final String[] spoofPackages = {"com.mgoogle", "app.revanced"};
private static final String currentGms;
private static final boolean needToSpoof = !isGmsInstalled() && (isFakeGmsInstalled() || isFakeGms2Installed());
static {
currentGms = getSpoofPackagePrefix();
}
private static boolean isPackageInstalled(String packageName) {
public static boolean isGmsInstalled() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo(packageName, 0);
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("com.google.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
}
} // Google Market Services check
public static boolean isGmsInstalled() {
return isPackageInstalled("com.google.android.gms");
}
public static boolean isFakeGmsInstalled() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("com.mgoogle.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
} // Microg Google Market Services check
public static boolean isSpoofPackageInstalled(String packageName) {
return isPackageInstalled(packageName + ".android.gms");
}
public static boolean isFakeGms2Installed() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("app.revanced.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
} // Microg Google Market Services check
public static boolean isAnyServicesInstalled() {
for (String packageName : spoofPackages) {
if (isSpoofPackageInstalled(packageName)) {
return true;
}
}
return false;
return isGmsInstalled() || isFakeGmsInstalled() || isFakeGms2Installed();
}
public static void fixGapps() {
@@ -52,10 +52,10 @@ public class GmsHook {
notificationManager.createNotificationChannel(new NotificationChannel("audio_playback_channel", AndroidUtils.getResources().getString(R.string.audio_message_play_error), NotificationManager.IMPORTANCE_LOW));
}
}
}
} // Music channels fix
public static String replaceGMSPackage(String str) {
return shouldSpoof() ? str.replaceAll("com.google", currentGms) : str;
return needToSpoof ? str.replaceAll("com.google", (isFakeGms2Installed() ? "app.revanced" : "com.mgoogle")) : str;
}
public static String getFirebaseInstanceIdReceiver() {
@@ -63,37 +63,6 @@ public class GmsHook {
}
public static Class getFirebaseInstanceIdReceiverClass() {
return getSpoofFirebaseInstanceIdReceiverClass();
return needToSpoof ? (isFakeGms2Installed() ? FirebaseInstanceIdReceiver3.class : FirebaseInstanceIdReceiver2.class) : FirebaseInstanceIdReceiver.class;
}
public static String getCurrentGms() {
return currentGms;
}
private static boolean shouldSpoof() {
return !isGmsInstalled() && (isSpoofPackageInstalled("com.mgoogle") || isSpoofPackageInstalled("app.revanced"));
}
private static String getSpoofPackagePrefix() {
for (String packageName : spoofPackages) {
if (isSpoofPackageInstalled(packageName)) {
return packageName;
}
}
return "com.google";
}
private static Class getSpoofFirebaseInstanceIdReceiverClass() {
switch (currentGms) {
case "com.mgoogle" -> {
return FirebaseInstanceIdReceiver2.class;
}
case "app.revanced" -> {
return FirebaseInstanceIdReceiver3.class;
}
default -> {
return FirebaseInstanceIdReceiver.class;
}
}
}
}
}
@@ -6,7 +6,6 @@ import android.os.Build;
import b.h.g.k.VKProgressDialog;
import com.aefyr.tsg.g2.TelegramStickersService;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.medianative.NativeLibLoader;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
@@ -18,8 +17,10 @@ import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.dialogs.Start;
import ru.vtosters.lite.utils.*;
import ru.vtosters.sponsorpost.internal.Native;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.NavigatorUtils;
import ru.vtosters.lite.utils.VTVerifications;
import ru.vtosters.sponsorpost.utils.Updates;
import static ru.vtosters.hooks.other.Preferences.checkupdates;
@@ -66,10 +67,6 @@ public class MainActivityInjector {
// VKIDProtection.alert(activity);
//needs to show selected tgs pack count in settings after cold launch
TelegramStickersService.getInstance(activity);
if (AccountManagerUtils.isLogin() && !Preferences.serverFeaturesDisable() && Preferences.isValidSignature()) {
Native.canVote = NativeLibLoader.loadLibrary("sponsorpost");
}
}
private static void updateBinsAndTmpArchive(Activity activity) {
@@ -83,7 +80,7 @@ public class MainActivityInjector {
ThemesManager.generateModApk(ThemesUtils.getReservedAccent());
activity.runOnUiThread(LifecycleUtils::restartApplication);
} catch (Throwable e) {
e.fillInStackTrace();
e.printStackTrace();
activity.runOnUiThread(() -> {
dialog.cancel();
new VkAlertDialog.Builder(activity)
@@ -1,10 +1,7 @@
package ru.vtosters.hooks;
import android.util.Log;
import android.util.SparseArray;
import com.vk.core.network.Network;
import com.vk.im.engine.internal.api_parsers.UserApiParser;
import com.vk.im.engine.models.users.User;
import com.vtosters.lite.R;
import okhttp3.Call;
import okhttp3.Callback;
@@ -13,6 +10,8 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
@@ -147,36 +146,16 @@ public class OnlineFormatterHook {
return json;
}
public static SparseArray<User> onlineHookList(String str) {
JSONArray jSONArray;
public static JSONArray onlineHookList(JSONArray jsonArr) {
if (!getBoolValue("onlinefix", false) || Preferences.serverFeaturesDisable()) return jsonArr;
try {
jSONArray = new JSONObject(str).getJSONArray("response");
} catch (JSONException e) {
throw new RuntimeException(e);
}
SparseArray<User> sparseArray = new SparseArray<>(jSONArray.length());
int length = jSONArray.length();
if (getBoolValue("onlinefix", false) || !Preferences.serverFeaturesDisable()) {
try {
jSONArray = JsonInjectors.setOnlineInfoUsers(jSONArray);
} catch (JSONException e) {
throw new RuntimeException(e);
}
JsonInjectors.setOnlineInfoUsers(jsonArr);
} catch (Exception e) {
Log.e("onlineHookProfiles", e.getMessage());
}
for (int i = 0; i < length; i++) {
JSONObject jSONObject;
try {
jSONObject = jSONArray.getJSONObject(i);
} catch (JSONException e) {
throw new RuntimeException(e);
}
User b = UserApiParser.b(jSONObject);
sparseArray.put(b.getId(), b);
}
return sparseArray;
return jsonArr;
}
public static JSONObject onlineHookProfiles(JSONObject json) {
@@ -209,19 +188,19 @@ public class OnlineFormatterHook {
try {
JsonInjectors.setOnlineInfoUsers(json.optJSONObject("read_requests").optJSONArray("items"));
} catch (Exception e) {
Log.e("onlineHookItemsRec", e.getMessage());
Log.e("onlineHookItems", e.getMessage());
}
try {
JsonInjectors.setOnlineInfoUsers(json.optJSONObject("recommendations").optJSONArray("items"));
} catch (Exception e) {
Log.e("onlineHookItemsRec", e.getMessage());
Log.e("onlineHookItems", e.getMessage());
}
try {
JsonInjectors.setOnlineInfoUsers(json.optJSONArray("profiles"));
} catch (Exception e) {
Log.e("onlineHookItemsRec", e.getMessage());
Log.e("onlineHookItems", e.getMessage());
}
return json;
@@ -1,16 +0,0 @@
package ru.vtosters.hooks;
import com.vtosters.lite.api.execute.GetWallInfo;
import java.util.ArrayList;
/**
* Since June 12, 2024, there are troubles with API method "execute.getUserInfo" in the old versions of VK Client (<6.16),
* to fix that issue we need to upgrade "execute.getUserInfo" for new API versions
* and implement the new API method "newsfeed.getPostTopics" (TODO)*/
@SuppressWarnings("unused")
public class PostTopicsHook {
public static void hook(final GetWallInfo.a model) throws Exception {
model.f = new ArrayList<>();
}
}
@@ -1,40 +1,29 @@
package ru.vtosters.hooks;
import androidx.core.util.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.RenameTool;
public class RenameHook {
public static void injectIntoJson(JSONObject obj) throws JSONException {
if (isEnabled()) {
int i = obj.getInt("id");
if (RenameTool.updateRequested) {
RenameTool.reloadDB();
}
Pair<String, String> user = RenameTool.renamedUsers.get(i);
if (user == null) return;
obj.put(RenameTool.COLUMN_FIRSTNAME, user.first).put(RenameTool.COLUMN_LASTNAME, user.second);
}
}
public static boolean isEnabled() {
return Preferences.getBoolValue("unstableNameChanger", true);
// int i = obj.getInt("id");
// if (RenameTool.updateRequested) {
// RenameTool.reloadDB();
// }
//
// Pair<String, String> user = RenameTool.renamedUsers.get(i);
// if (user == null) return;
// obj.put(RenameTool.COLUMN_FIRSTNAME, user.first).put(RenameTool.COLUMN_LASTNAME, user.second);
}
public static void injectIntoJsonGroup(JSONObject obj) throws JSONException {
if (isEnabled()) {
int i = obj.getInt("id");
if (RenameTool.updateRequested) {
RenameTool.reloadDB();
}
String user = RenameTool.renamedGroups.get(i);
if (user == null) return;
obj.put("name", user);
}
// int i = obj.getInt("id");
//
// if (RenameTool.updateRequested) {
// RenameTool.reloadDB();
// }
//
// String user = RenameTool.renamedGroups.get(i);
// if (user == null) return;
// obj.put("name", user);
}
}
@@ -1,29 +0,0 @@
package ru.vtosters.hooks;
import android.os.Build;
import me.leolin.shortcutbadger.Badger;
import me.leolin.shortcutbadger.impl.*;
import java.util.List;
public class ShortcutBadgerHook {
public static void init(List<Class<? extends Badger>> list) {
list.add(AdwHomeBadger.class);
list.add(ApexHomeBadger.class);
list.add(DefaultBadger.class);
list.add(NewHtcHomeBadger.class);
list.add(NovaHomeBadger.class);
list.add(AsusHomeBadger.class);
list.add(HuaweiHomeBadger.class);
list.add(OPPOHomeBader.class);
list.add(SamsungHomeBadger.class);
list.add(ZukHomeBadger.class);
list.add(VivoHomeBadger.class);
list.add(ZTEHomeBadger.class);
list.add(EverythingMeHomeBadger.class);
if (Build.VERSION.SDK_INT < 34) {
list.add(SonyHomeBadger.class);
}
}
}
@@ -3,17 +3,15 @@ package ru.vtosters.hooks;
import com.vk.medianative.MediaImageEncoder;
import com.vk.toggle.FeatureManager;
import com.vk.toggle.Features;
import ru.vtosters.hooks.other.Preferences;
import static ru.vtosters.hooks.other.Preferences.*;
public class TogglesHook {
public static boolean shouldPatch(Features.Type feature) {
return switch (feature) {
case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL, AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE ->
case AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE ->
true;
default ->
false;
default -> false;
};
}
@@ -25,10 +23,9 @@ public class TogglesHook {
case FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED -> !milkshake();
case AB_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_COMPACT_REPOST, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT ->
postsredesign();
case FEATURE_IMAGE_QUALITY_UPGRADE -> !MediaImageEncoder.needToCompress();
case FEATURE_IMAGE_QUALITY_UPGRADE -> MediaImageEncoder.needToSkipCompression();
case FEATURE_MENU_GAMES_CAROUSEL -> miniapps();
case FEATURE_TRAFFIC_SAVER -> !disableForceTrafficSaver();
case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL -> Preferences.getBoolValue("batchmessages", false);
default -> true;
};
}
@@ -24,7 +24,9 @@ public class VKUIHook {
private static void applyVKUIStyles(WebView webView) {
if (getBoolValue("VKUI_INJ", true) && webView != null && webView.getUrl() != null && !webView.getUrl().contains("static.vk.com/memories")) {
WebViewColoringUtils.load();
if (!WebViewColoringUtils.isLoaded) {
WebViewColoringUtils.load();
}
loadAndApplyCSS(webView);
}
}
@@ -9,7 +9,6 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
import com.vtosters.lite.R;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.VKXUtils;
@@ -20,29 +19,18 @@ import static ru.vtosters.hooks.other.Preferences.milkshake;
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
public class MusicBottomSheetHook {
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
public static ArrayList<MusicAction> hook(final ArrayList<MusicAction> actions, final MusicTrack musicTrack) {
if (musicTrack.F1()) return actions;
String trackId = asId(musicTrack);
final var trackId = asId(musicTrack);
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
if (isIntegrationEnabled()) {
if (VKXUtils.isVkxCached(trackId)) {
actions.add(getRemoveCacheTrackVkxAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackVkxAction());
}
if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction());
else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction());
} else {
if (MusicCacheImpl.isCachedTrack(trackId)) {
actions.add(getRemoveCacheTrackAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
}
if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction());
else if (isNetworkConnected()) actions.add(addToCacheTrackAction());
}
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
return actions;
}
@@ -84,12 +72,8 @@ public class MusicBottomSheetHook {
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackVkxAction());
}
} else {
if (PlaylistCacheDbDelegate.isCachedPlaylist(AndroidUtils.getGlobalContext(), playlist.v1())) {
actions.add(getRemoveCacheTrackAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
}
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
}
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
@@ -150,24 +134,20 @@ public class MusicBottomSheetHook {
}
public static boolean injectOnClick(int actionId, Playlist playlist) { // playlist inj
if (actionId == R.id.play_in_vkx) {
if (actionId == R.id.play_in_vkx)
return tryPlayInVKX(null, null, playlist);
}
if (actionId == R.id.remove_from_cache) {
PlaylistCacheDbDelegate.deletePlaylist(AndroidUtils.getGlobalContext(), playlist.v1());
return true;
} else if (actionId == R.id.add_to_cache) {
if (actionId == R.id.add_to_cache) {
AudioDownloader.cachePlaylist(playlist);
return true;
}
if (actionId == R.id.remove_from_cache_vkx) {
getInstance().runOnService(service -> {
try {
service.deletePlaylistFromCache(playlist.a, playlist.b);
} catch (RemoteException e) {
e.fillInStackTrace();
e.printStackTrace();
}
});
return true;
@@ -176,7 +156,7 @@ public class MusicBottomSheetHook {
try {
service.addPlaylistToCache(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
} catch (RemoteException e) {
e.fillInStackTrace();
e.printStackTrace();
}
});
return true;
@@ -188,7 +168,7 @@ public class MusicBottomSheetHook {
try {
service.downloadPlaylist(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
} catch (RemoteException e) {
e.fillInStackTrace();
e.printStackTrace();
}
});
} else {
@@ -278,4 +258,4 @@ public class MusicBottomSheetHook {
false
);
}
}
}
@@ -4,20 +4,19 @@ import android.util.Log;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.network.Network;
import com.vk.core.util.DeviceIdProvider;
import okhttp3.Headers;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.Objects;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.DateHook.getLocale;
@@ -41,15 +40,9 @@ public class CatalogJsonInjector {
removeUnsupportedLayouts(blocks);
if (Preferences.getBoolValue("playlistsCatalogs", true)) {
fetchCatalogId("https://vk.com/audio?section=my_playlists", oldItems);
fetchCatalogId("https://vk.com/audio?section=my_playlists", oldItems);
fetchCatalogId("https://vk.com/audio?section=albums", oldItems);
}
if (Preferences.sendMusicMetrics() && Preferences.getBoolValue("playStatCatalog", false)) {
fetchCatalogId("https://vk.com/audio?section=recent", oldItems);
}
fetchCatalogId("https://vk.com/audio?section=albums", oldItems);
setDefaultAudioPage(oldItems, catalog);
}
@@ -58,16 +51,18 @@ public class CatalogJsonInjector {
var noPlaylists = !json.has("playlists");
if (noPlaylists) {
PlaylistHelper.addCachedPlaylists(new JSONArray());
json.put("playlists", new JSONArray().put(getPlaylist()));
Log.d("catalogInjector", "added new pl");
} else {
PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"));
json.optJSONArray("playlists").put(getPlaylist());
Log.d("catalogInjector", "added to exist pl");
}
if (!useOldAppVer || noPlaylists) {
var newBlocks = new JSONArray();
newBlocks
.put(getCatalogHeader("Кешированные плейлисты"))
.put(getCatalogHeader())
.put(getCatalogPlaylist())
.put(getCatalogSeparator());
@@ -94,7 +89,6 @@ public class CatalogJsonInjector {
var blocks = section.getJSONArray("blocks");
fixDailyMix(blocks);
removeUnsupportedLayouts(blocks);
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
return; // early return if not users catalog or no tracks or integration enabled
@@ -103,16 +97,22 @@ public class CatalogJsonInjector {
var noPlaylists = !json.has("playlists");
if (noPlaylists) {
PlaylistHelper.addCachedPlaylists(new JSONArray());
json.put("playlists", new JSONArray().put(getPlaylist()));
Log.d("catalogInjector", "added new pl");
} else {
PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"));
try {
json.optJSONArray("playlists").put(getPlaylist());
Log.d("catalogInjector", "added to exist pl");
} catch (Exception e) {
Log.d("catalogInjector", e.toString());
}
}
if (!useOldAppVer && !noPlaylists) {
var newBlocks = new JSONArray();
newBlocks
.put(getCatalogHeader("Кешированные плейлисты"))
.put(getCatalogHeader())
.put(getCatalogPlaylist())
.put(getCatalogSeparator());
@@ -130,15 +130,12 @@ public class CatalogJsonInjector {
if (type.equals("music_playlists") && j.has("playlists_ids")) {
var newarr = new JSONArray();
var playlists_ids = j.getJSONArray("playlists_ids");
var savedPlaylists = PlaylistHelper.getCachedPlaylistsIds();
var playlists_ids = j.optJSONArray("playlists_ids");
for (int n = 0; n < savedPlaylists.length(); n++) {
newarr.put(savedPlaylists.getString(n));
}
newarr.put(getUserId() + "_-1");
for (int n = 0; n < playlists_ids.length(); n++) {
newarr.put(playlists_ids.getString(n));
newarr.put(playlists_ids.optString(n));
}
Log.d("catalogInjector", "added to pl ids");
@@ -220,35 +217,16 @@ public class CatalogJsonInjector {
}
}
private static void removeUnsupportedLayouts(JSONArray blocks) {
for (int i = blocks.length() - 1; i >= 0; i--) {
JSONObject block = blocks.optJSONObject(i);
if (block == null) continue;
JSONObject layout = block.optJSONObject("layout");
if (layout == null) continue;
String data_type = block.optString("data_type");
if (Objects.equals(data_type, "music_recommended_playlists") || Objects.equals(data_type, "radiostations")) {
blocks.remove(i); // block
if (i - 1 >= 0) blocks.remove(i - 1); // text
if (i - 2 >= 0) blocks.remove(i - 2); // divider
continue; // Skip to the next iteration after removing
}
String name = layout.optString("name");
if (!name.equals("header_extended")) {
continue; // Skip to the next iteration if not header_extended
}
if (layout.has("top_title")) {
blocks.remove(i);
}
try {
layout.put("name", "header");
} catch (JSONException e) {
e.fillInStackTrace();
private static void removeUnsupportedLayouts(JSONArray blocks) throws JSONException {
for (int i = 0; i < blocks.length(); i++) {
var block = blocks.optJSONObject(i);
var layout = block.optJSONObject("layout");
if (layout != null) {
var name = layout.optString("name");
if (name.equals("header_extended")) {
if (layout.has("top_title")) blocks.remove(i);
layout.put("name", "header");
}
}
}
}
@@ -285,9 +263,7 @@ public class CatalogJsonInjector {
+ "&access_token="
+ AccountManagerUtils.getUserToken())
.a("Accept-Encoding", "gzip")
.a("User-Agent", Network.l.c().a())
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.a();
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
var json = new JSONObject(GzipDecompressor.decompressResponse(resp)).getJSONObject("response");
var catalogarr = json.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
@@ -15,20 +15,18 @@ import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.AccountManagerUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PlaylistInjector {
private static final ExecutorService executor = Executors.newCachedThreadPool();
public final static String CHANNEL_NAME = "VTCH";
private static final ExecutorService executor = Executors.newCachedThreadPool();
public static void injectDownloadPlaylist(Playlist playlist) {
AudioDownloader.cachePlaylist(playlist);
executor.submit(() -> AudioDownloader.cachePlaylist(playlist));
}
public static boolean eligibleForOfflineCaching() {
@@ -37,10 +35,8 @@ public class PlaylistInjector {
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
try {
if (MusicCacheImpl.isEmpty()) {
if (!eligibleForOfflineCaching())
return null;
}
var requestArgs = audioGetPlaylist.b();
var id = requestArgs.get("id");
@@ -48,11 +44,11 @@ public class PlaylistInjector {
var accessKey = requestArgs.get("access_key");
boolean isVirtualPlaylist = accessKey != null && (accessKey.equals("cache"));
boolean isAlbumVirtualPlaylist = accessKey != null && (accessKey.equals("cacheAlbum"));
boolean isOwnCachePlaylist = Objects.equals(ownerId, String.valueOf(AccountManagerUtils.getUserId())) && Objects.equals(id, "-1");
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist)) {
Log.d("PlaylistInjector", "id = " + id + " / owner = " + ownerId + " / access = " + accessKey + " / " + isVirtualPlaylist + " + " + isAlbumVirtualPlaylist);
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist))
return null;
}
return Observable.a((ObservableOnSubscribe<AudioGetPlaylist.c>) observableEmitter -> {
AudioGetPlaylist.c response = new AudioGetPlaylist.c();
@@ -77,7 +73,7 @@ public class PlaylistInjector {
response.c = tracks;
} catch (RemoteException | JSONException e) {
e.fillInStackTrace();
e.printStackTrace();
response.c = new ArrayList<>();
}
observableEmitter.b(response);
@@ -86,18 +82,18 @@ public class PlaylistInjector {
return;
}
if (isOwnCachePlaylist) {
if (isVirtualPlaylist) {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getMyCachedMusicTracks();
response.b = PlaylistHelper.createCachedPlaylistMetadata();
} else {
response.c = (ArrayList<MusicTrack>) MusicCacheImpl.getPlaylistSongs(ownerId, id);
response.b = MusicCacheImpl.getPlaylist(id, ownerId);
response.c = (ArrayList<MusicTrack>) TracklistHelper.getTracks(requestArgs.get("owner_id"));
response.b = PlaylistHelper.createCachedPlaylistMetadata(requestArgs.get("owner_id"));
}
observableEmitter.b(response);
});
} catch (Exception e) {
e.fillInStackTrace();
e.printStackTrace();
}
return null;
}
@@ -12,7 +12,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.AccountManagerUtils;
@@ -23,8 +22,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*;
public class TracklistInjector {
public static boolean eligibleForOfflineCaching() {
@@ -42,7 +39,7 @@ public class TracklistInjector {
Log.d("TracklistInjector", "Using vkx cache catalog");
observableEmitter.b(parser.c(new JSONObject(service.getCacheCatalog())));
} catch (Exception e) {
e.fillInStackTrace();
e.printStackTrace();
List<MusicTrack> tracks = new ArrayList<>();
List<String> cache = service.getCache();
@@ -55,14 +52,14 @@ public class TracklistInjector {
observableEmitter.b(parser.c(createVirtualCatalog(tracks)));
}
} catch (Exception e) {
e.fillInStackTrace();
e.printStackTrace();
}
});
} else {
if (MusicCacheImpl.isEmpty()) {
observableEmitter.b(parser.c(getEmptyCatalog()));
} else {
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getMyCachedMusicTracks())));
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
}
}
});
@@ -165,12 +162,16 @@ public class TracklistInjector {
boolean noPlaylists = hasNoPlaylists(blocks);
if (noPlaylists) {
PlaylistHelper.addCachedPlaylists(new JSONArray());
catalogNode.put("playlists", new JSONArray().put(PlaylistHelper.getPlaylist()));
firstSection.put("blocks", createNewBlocks(blocks));
JSONArray newBlocks = createNewBlocks(blocks);
Log.d("catalogInjector", "added cache catalog playlist");
firstSection.put("blocks", newBlocks);
} else {
try {
PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"));
catalogNode.optJSONArray("playlists").put(PlaylistHelper.getPlaylist());
updatePlaylistsIds(blocks);
} catch (Exception e) {
@@ -187,7 +188,7 @@ public class TracklistInjector {
return firstSection.getString("url").equals("https://vk.com/audios" + AccountManagerUtils.getUserId() + "?section=all");
}
private static boolean hasNoPlaylists(JSONArray blocks) {
private static boolean hasNoPlaylists(JSONArray blocks) throws JSONException {
return blocks.optJSONObject(0).optJSONArray("buttons").optJSONObject(0).optJSONObject("action").optString("type").equals("create_playlist");
}
@@ -195,14 +196,13 @@ public class TracklistInjector {
JSONArray newBlocks = new JSONArray();
newBlocks
.put(PlaylistHelper.getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title)))
.put(PlaylistHelper.getCatalogHeader())
.put(PlaylistHelper.getCatalogPlaylist())
.put(PlaylistHelper.getCatalogSeparator());
for (int i = 0; i < blocks.length(); i++) {
newBlocks.put(blocks.optJSONObject(i));
}
return newBlocks;
}
@@ -212,15 +212,16 @@ public class TracklistInjector {
String type = j.optString("data_type");
if (type.equals("music_playlists") && j.has("playlists_ids")) {
JSONArray newarr = PlaylistHelper.getCachedPlaylistsIds();
JSONArray newarr = new JSONArray();
JSONArray playlists_ids = j.optJSONArray("playlists_ids");
newarr.put(AccountManagerUtils.getUserId() + "_-1");
for (int n = 0; n < playlists_ids.length(); n++) {
newarr.put(playlists_ids.optString(n));
}
j.put("playlists_ids", newarr);
break;
}
}
@@ -239,19 +240,7 @@ public class TracklistInjector {
audiosBlock.put("layout", layout);
var blocks = new JSONArray();
if (MusicCacheImpl.hasPlaylist()) {
blocks.put(getCatalogHeader("Кешированные плейлисты"))
.put(getCatalogPlaylist());
}
if (!PlaylistCacheDbDelegate.isPlaylistEmpty(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1")) {
if (MusicCacheImpl.hasPlaylist()) blocks.put(getCatalogSeparator());
blocks.put(getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title)))
.put(getShuffleButton())
.put(audiosBlock);
}
var blocks = new JSONArray().put(getShuffleButton()).put(audiosBlock);
var randomId = getRandomId();
@@ -270,7 +259,6 @@ public class TracklistInjector {
return new JSONObject()
.put("catalog", catalog)
.put("playlists", addCachedPlaylists(new JSONArray()))
.put("audios", TracklistHelper.tracksToJsons(tracks));
}
@@ -6,8 +6,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.StrictMode;
import com.vk.core.util.AppContextHolder;
import com.vk.medianative.MediaImageEncoder;
import com.vk.medianative.MediaNative;
import com.vtosters.lite.data.Users;
@@ -24,8 +22,7 @@ import java.security.NoSuchAlgorithmException;
public class Preferences {
public static void init(Application application) throws Exception {
AppContextHolder.a = application.getApplicationContext();
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy); // fix profiles hide hook
MediaNative.init(application);
@@ -100,8 +97,8 @@ public class Preferences {
return getBoolValue("vkme", false);
}
public static int autocache() {
return Preferences.getPreferences().getInt("autocaching", 0);
public static boolean autocache() {
return getBoolValue("autocache", false);
}
public static boolean adsstories() {
@@ -292,10 +289,6 @@ public class Preferences {
return getBoolValue("vkme_notifs", false);
}
public static boolean sendMusicMetrics() {
return getBoolValue("sendMusicMetrics", true);
}
public static boolean screenshotdetect() {
return getBoolValue("screenshotdetect", true);
}
@@ -363,14 +356,7 @@ public class Preferences {
}
public static int compress(int origquality) {
return MediaImageEncoder.needToCompress() ? origquality : 100;
}
public static String metadataSeparator() {
return getPreferences().getString("metadata_separator", "; ");
}
public static void setMetadataSeparator(String separator) {
getPreferences().edit().putString("metadata_separator", separator).apply();
if (MediaImageEncoder.needToSkipCompression()) return 100;
return origquality;
}
}
@@ -24,8 +24,6 @@ import com.vk.core.drawable.RecoloredDrawable;
import com.vk.core.ui.themes.MilkshakeHelper;
import com.vk.core.ui.themes.VKTheme;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.im.engine.models.dialogs.DialogTheme;
import com.vk.im.ui.themes.DefaultThemeProvider;
import com.vtosters.lite.R;
import com.vtosters.lite.data.ThemeTracker;
import ru.vtosters.lite.deviceinfo.OEMDetector;
@@ -34,6 +32,7 @@ import ru.vtosters.lite.themes.ThemesHacks;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.wallpapers.WallpapersHooks;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.WebViewColoringUtils;
import java.lang.reflect.Field;
@@ -45,7 +44,7 @@ public class ThemesUtils {
try {
setTheme(theme, LifecycleUtils.getCurrentActivity(), restartActivity);
} catch (Exception e) {
e.fillInStackTrace();
e.printStackTrace();
}
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
@@ -62,27 +61,14 @@ public class ThemesUtils {
activity = LifecycleUtils.getCurrentActivity();
}
VKThemeHelper.theme(theme, activity, fl);
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
ThemesCore.clear();
ThemesCore.init();
DialogTheme.d.a(new DefaultThemeProvider(VKThemeHelper.k).b()); // reset dialog attrs cache
}
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) ThemesCore.clear();
if (restartActivity) activity.recreate();
ThemeTracker.a();
if (restartActivity) {
activity.recreate();
}
WebViewColoringUtils.isLoaded = false;
new WebView(activity).clearCache(true);
WebCachePreloader.e();
}
public static DialogTheme getDialogTheme() {
if (isMonetTheme()) {
return new DefaultThemeProvider(VKThemeHelper.k).b(); // get directly from theme cuz monet issue
} else {
return DialogTheme.d.a(); // get cached theme
}
}
public static boolean isDarkTheme() {
return VKThemeHelper.r();
}
@@ -355,7 +341,7 @@ public class ThemesUtils {
}
public static int getColor(int i) {
return ThemesHacks.getHackedColor(getGlobalContext(), i);
return ThemesHacks.getHackedColor(LifecycleUtils.getCurrentActivity(), i);
} // Android Support color injector + accent color checker
public static int getAlertStyle() {
@@ -1,11 +1,10 @@
package ru.vtosters.lite.concurrent;
import com.vk.core.concurrent.VkExecutors;
import io.reactivex.Scheduler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import io.reactivex.Scheduler;
import java.util.concurrent.Executors;
public class VTExecutors {
public static ExecutorService getMusicDownloadExecutor() {
@@ -33,21 +32,7 @@ public class VTExecutors {
}
private static class LazyHolder {
private static final int MIN_PARALLELISM = 1;
private static final int MAX_PARALLELISM = 7;
private static final int PARALLELISM = clamp(
(Runtime.getRuntime().availableProcessors() >> 1) - 1
);
private static final ExecutorService INSTANCE =
new ForkJoinPool(PARALLELISM,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
(t, e) -> {},
/* FIFO */ true);
private static int clamp(int value) {
return Math.min(LazyHolder.MAX_PARALLELISM, Math.max(value, LazyHolder.MIN_PARALLELISM));
}
public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
}
}
}
@@ -115,7 +115,7 @@ public class Device {
+ "- Manufacturer: " + this.manufacturerName + "\n"
+ "- Brand: " + this.brandName + "\n"
+ "- Model: " + this.modelName + "\n"
+ (isMIUI() || isHyperOs() ? "- Miui/HyperOS Version Code: " + this.MiuiUiVersionCode + "\n" + "- Miui/HyperOS Version Name: " + this.MiuiUiVersionName + "\n" : "")
+ (isMIUI() ? "- Miui Version Code: " + this.MiuiUiVersionCode + "\n" + "- Miui Version Name: " + this.MiuiUiVersionName + "\n" : "")
+ (isSamsung() ? "- OneUi Major Version: " + this.OneUiMajorVersion + "\n" + "- OneUi Minor Version: " + this.OneUiMinorVersion + "\n" : "")
+ (hasMiuiIncrCode() ? "- Miui Version Incremental Code: " + this.MiuiIncrementalCode + "\n" : "")
+ (isEMUI() ? "- Emui Version: " + this.EmuiVersion : "");
@@ -56,7 +56,7 @@ public class OEMDetector {
}
public static boolean isOEM() {
return isMIUI() || isEMUI() || isFlyme() || isZenUI() || isSamsung() || isVivo() || isOneUi() || isHyperOs();
return isMIUI() || isEMUI() || isFlyme() || isZenUI() || isSamsung() || isVivo() || isOneUi();
}
public static boolean isMIUI() {
@@ -68,15 +68,6 @@ public class OEMDetector {
}
}
public static boolean isHyperOs() {
try {
int miuiVer = Integer.parseInt(getMiuiUiVersionCode()); // cuz hyperos gives 816 and etc
return miuiVer > 14 && isMIUI();
} catch (Exception unused) {
return false;
}
}
public static boolean isIntentResolved(Context ctx, Intent intent) {
return (intent != null && ctx.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null);
}
@@ -133,6 +124,7 @@ public class OEMDetector {
try {
return (String) getPropMtd.invoke(null, prop);
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
@@ -1,36 +1,27 @@
package ru.vtosters.lite.downloaders;
import android.os.Environment;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.util.ToastUtils;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.R;
import org.json.JSONObject;
import ru.vtosters.hooks.music.MusicCacheFilesHook;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
import ru.vtosters.lite.music.downloader.AudioGet;
import ru.vtosters.lite.music.downloader.PlaylistDownloader;
import ru.vtosters.lite.music.downloader.ThumbnailPlaylistDownloader;
import ru.vtosters.lite.music.downloader.TrackDownloader;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
@@ -41,18 +32,19 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString;
*/
public class AudioDownloader {
public static final ExecutorService executor = Executors.newCachedThreadPool();
public static final String dlpath = Preferences.getBoolValue("dldir", false) ? Environment.DIRECTORY_DOWNLOADS : Environment.DIRECTORY_MUSIC;
public static void downloadPlaylist(Playlist playlist) {
List<MusicTrack> tracks = PlaylistConverter.getPlaylist(playlist);
var tracks = PlaylistConverter.getPlaylist(playlist);
String playlistName = IOUtils.getValidFileName(playlist.g);
var playlistName = IOUtils.getValidFileName(playlist.g);
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
String downloadPath = musicPath + File.separator + playlistName;
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
var downloadPath = musicPath + File.separator + playlistName;
int notificationId = playlistName.hashCode();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
var notificationId = playlistName.hashCode();
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
PlaylistDownloader.downloadPlaylist(
tracks,
@@ -68,96 +60,54 @@ public class AudioDownloader {
}
public static void cacheTrack(MusicTrack track) {
String trackId = LibVKXClient.asId(track);
var trackId = LibVKXClient.asId(track);
if (MusicCacheImpl.isCachedTrack(trackId)) {
MusicCacheImpl.removeTrack(trackId);
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
} else {
File trackFile = MusicCacheFilesHook.getTrackFile(trackId);
if (!trackFile.exists()) {
trackFile.getParentFile().mkdirs();
}
downloadM3U8(track, true);
return;
}
var trackFile = MusicCacheFilesHook.getTrackFile(trackId);
if (!trackFile.exists())
trackFile.getParentFile().mkdirs();
executor.submit(() -> downloadM3U8(track, true));
}
public static void cachePlaylist(Playlist playlist) {
List<MusicTrack> tracks = PlaylistConverter.getPlaylist(playlist);
var tracks = PlaylistConverter.getPlaylist(playlist);
int notificationId = playlist.g.hashCode();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
JSONObject thumbs = PlaylistUtils.getThumb(playlist);
if (thumbs != null) {
new ThumbnailPlaylistDownloader().download(null, new Callback() {
@Override
public void onProgress(int progress) {
}
@Override
public void onSuccess() {
PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist);
Log.d("Playlist", "adding to cache with thumbs " + playlist.v1());
}
@Override
public void onFailure(Throwable e) {
throw new RuntimeException(e);
}
@Override
public void onSizeReceived(long size, long header) {
}
}, playlist);
} else {
PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist);
Log.d("Playlist", "adding to cache without thumbs " + playlist.a);
}
var notificationId = playlist.g.hashCode();
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
PlaylistDownloader.cachePlaylist(
tracks,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId),
playlist
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
);
}
public static void cacheAllAudios() {
List<MusicTrack> tracks = AudioGet.getAudios();
var tracks = AudioGet.getAudios();
int notificationId = AccountManagerUtils.getUserId();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
if (tracks == null || tracks.isEmpty()) {
return;
}
var notificationId = AccountManagerUtils.getUserId();
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
PlaylistDownloader.cachePlaylist(
tracks,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId),
PlaylistHelper.createCachedPlaylistMetadata()
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
);
}
public static void downloadAllAudios() {
List<MusicTrack> tracks = AudioGet.getAudios();
var tracks = AudioGet.getAudios();
int notificationId = AccountManagerUtils.getUserId();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
var notificationId = AccountManagerUtils.getUserId();
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
String playlistName = "Audios of " + AccountManagerUtils.getUserId();
var playlistName = "Audios of " + AccountManagerUtils.getUserId();
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
String downloadPath = musicPath + File.separator + playlistName;
if (tracks == null || tracks.isEmpty()) {
return;
}
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
var downloadPath = musicPath + File.separator + playlistName;
PlaylistDownloader.downloadPlaylist(
tracks,
@@ -168,20 +118,20 @@ public class AudioDownloader {
}
private static void downloadM3U8(MusicTrack track, boolean cache) {
Objects.requireNonNull(track.D, () -> {
if (track.D == null) {
ToastUtils.a(getString(R.string.link_audio_error));
return "link must not be null";
});
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
int tempId = track.d;
String downloadPath = musicPath + File.separator;
NotificationCompat.Builder notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
return;
}
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
var tempId = track.d;
var downloadPath = musicPath + File.separator;
var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
Callback cb = MusicCallbackBuilder.buildOneTrackCallback(tempId, notification);
if (cache) {
TrackDownloader.cacheTrack(track, cb, PlaylistHelper.createCachedPlaylistMetadata());
TrackDownloader.cacheTrack(track, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
} else {
TrackDownloader.downloadTrack(track, downloadPath, cb);
TrackDownloader.downloadTrack(track, downloadPath, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
}
}
}
}
@@ -5,7 +5,10 @@ import android.os.Environment;
import com.vk.core.network.Network;
import com.vk.dto.common.ImageSize;
import com.vk.dto.stories.model.StoryEntry;
import okhttp3.*;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.File;
@@ -38,43 +41,45 @@ public class StoryDownloader {
.b(url)
.a();
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
try (Response response = client.a(request).execute()) {
if (!response.h()) {
throw new IOException("Unexpected code " + response);
}
ResponseBody responseBody = response.a();
if (responseBody == null) {
throw new IOException("No response body");
}
String fileName = "photo" + story.E.a + ".jpg";
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/Stories/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, fileName);
try (InputStream inputStream = responseBody.a();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
MediaScannerConnection.scanFile(AndroidUtils.getGlobalContext(), new String[]{outputFile.toString()}, null, null);
AndroidUtils.sendToast("Файл скачан в Pictures/Stories");
} catch (IOException e) {
e.fillInStackTrace();
}
@Override
public void a(Call call, Response response) {
ResponseBody responseBody = response.a();
String fileName = "photo" + story.E.a + ".jpg";
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/Stories/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, fileName);
try (InputStream inputStream = responseBody.a();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
MediaScannerConnection.scanFile(AndroidUtils.getGlobalContext(), new String[]{outputFile.toString()}, null, null);
AndroidUtils.sendToast("Файл скачан в Pictures/Stories");
} catch (IOException e) {
e.fillInStackTrace();
}
}
});
} catch (IOException e) {
e.fillInStackTrace();
}
}
}
@@ -1,11 +1,8 @@
package ru.vtosters.lite.downloaders;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
@@ -13,7 +10,6 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.dialogs.bottomsheet.MenuBottomSheetAction;
import com.vk.core.network.Network;
@@ -44,9 +40,6 @@ import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
public class VideoDownloader {
public static int DOWNLOAD_ID = 0;
public static int OPEN_EXTERNAL_LINK_ID = 1;
public static String channelId = "video_download_channel";
public static String channelName = "Video Download Channel";
public static int notificationId = 1;
public static void addAction(List<MenuBottomSheetAction> actions, int... ints) {
actions.add(new MenuBottomSheetAction(ints[0], ints[1], ints[2], ints[3]));
@@ -81,86 +74,46 @@ public class VideoDownloader {
.setAdapter(adapter, (dialog, which) -> {
String url = urls.get(which);
NotificationManager notificationManager = (NotificationManager) AndroidUtils.getGlobalContext().getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(notificationChannel);
}
String notificationTitle = "Загрузка видео";
String notificationText = "Загрузка началась";
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), channelId)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setProgress(0, 0, true)
.setOngoing(true);
notificationManager.notify(notificationId, notificationBuilder.build());
OkHttpClient client = Network.b(Network.ClientType.CLIENT_IMAGE_LOADER);;
Request request = new Request.a()
.b(url)
.a();
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
try (Response response = client.a(request).execute()) {
if (!response.h()) {
throw new IOException("Unexpected code " + response);
}
ResponseBody responseBody = response.a();
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "/VKVideo/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, videoFile + ".mp4");
try (InputStream inputStream = responseBody.a();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
MediaScannerConnection.scanFile(finalContext, new String[]{outputFile.toString()}, null, null);
AndroidUtils.sendToast("Файл скачан в Movies/VKVideo");
} catch (IOException e) {
e.fillInStackTrace();
}
@Override
public void a(Call call, Response response) {
ResponseBody responseBody = response.a();
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "/VKVideo/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, videoFile + ".mp4");
try (InputStream inputStream = responseBody.a();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesRead = 0;
long fileSize = responseBody.d();
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
int progress = (int) ((totalBytesRead * 100) / fileSize);
notificationBuilder.setProgress(100, progress, false)
.setContentText("Загружено " + progress + "%");
notificationManager.notify(notificationId, notificationBuilder.build());
}
outputStream.flush();
MediaScannerConnection.scanFile(finalContext, new String[]{outputFile.toString()}, null, null);
notificationManager.cancel(notificationId);
String successMessage = "Файл успешно скачан";
notificationBuilder.setContentText(successMessage)
.setProgress(0, 0, false)
.setContentTitle(notificationTitle)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setOngoing(false);
notificationManager.notify(notificationId, notificationBuilder.build());
} catch (IOException e) {
e.fillInStackTrace();
}
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.show();
}
@@ -288,4 +241,4 @@ public class VideoDownloader {
}
});
}
}
}
@@ -2,25 +2,22 @@ package ru.vtosters.lite.music;
import android.text.TextUtils;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URLEncoder;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.net.URLEncoder;
public class Genius {
private static final String KEY = "Bearer ZTejoT_ojOEasIkT9WrMBhBQOz6eYKK5QULCMECmOhvwqjRZ6WbpamFe3geHnvp3";
private static final String URL = "https://api.genius.com";
@@ -28,7 +25,7 @@ public class Genius {
public static String getTextMusic(MusicTrack musictrack) {
String uid = musictrack.y1();
String artist = removeExtras(MusicTrackUtils.getArtists(musictrack));
String artist = removeExtras(M3UDownloader.getArtists(musictrack));
String title = removeExtras(musictrack.f);
int duration = musictrack.h;
@@ -3,14 +3,19 @@ package ru.vtosters.lite.music;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -19,21 +24,6 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
public class LastFMScrobbler {
private static final String KEY = "5965d63402414776c54c266db0211746";
private static final String KEY_SECRET = "d982180eed13275bb948e41cf225f88f";
@@ -43,7 +33,7 @@ public class LastFMScrobbler {
public static void grabMusicTrack(MusicTrack musictrack) {
var uid = musictrack.y1();
var artist = MusicTrackUtils.getArtists(musictrack);
var artist = M3UDownloader.getArtists(musictrack);
var title = musictrack.f;
var duration = musictrack.h;
var track_id = musictrack.d;
@@ -51,14 +41,13 @@ public class LastFMScrobbler {
var access_key = !TextUtils.isEmpty(musictrack.J) ? musictrack.J : "";
var album_id = musictrack.I;
var album = album_id != null ? album_id.getTitle() : null;
var isOwnTrack = musictrack.e == AccountManagerUtils.getUserId();
if (TextUtils.isEmpty(uid) || TextUtils.isEmpty(artist) || TextUtils.isEmpty(title) || duration == 0) {
Log.d("Scrobbler", "grabTrackInfo: " + "Empty track, info: " + artist + " - " + title + " - " + duration + " - " + uid);
return;
}
if (needToCache(isOwnTrack) && !MusicCacheImpl.isCachedTrack(uid)) {
if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) {
if (LibVKXClient.isIntegrationEnabled()) {
LibVKXClient.getInstance().runOnService(service -> {
try {
@@ -75,12 +64,6 @@ public class LastFMScrobbler {
scrobbleTrack(duration, artist, title, uid, album);
}
private static boolean needToCache(boolean isOwnTrack) {
int autocache = Preferences.autocache();
return autocache == 2 || (autocache == 1 && isOwnTrack);
}
public static void scrobbleTrack(
long duration,
String artist,
@@ -1,16 +1,16 @@
package ru.vtosters.lite.music.cache.delegate;
package ru.vtosters.lite.music.cache;
import android.content.Context;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
import java.util.List;
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
public class MusicCacheDbDelegate {
private static MusicCacheDb connectToDb(Context context) {
return new MusicCacheDb(context);
static synchronized private MusicCacheDb connectToDb(Context context) {
synchronized (MusicCacheDb.class) {
return new MusicCacheDb(context);
}
}
public static void addTrack(Context context,
@@ -43,9 +43,27 @@ public class MusicCacheDbDelegate {
}
}
public static MusicTrack getTrackById(Context context, String trackId) {
public static List<MusicTrack> getAllTracks(Context context) {
try (var db = connectToDb(context)) {
return db.getTrackById(trackId);
return db.getAllTracks();
}
}
public static List<MusicTrack> getAlbumById(Context context, String albumId) {
try (var db = connectToDb(context)) {
return db.getAlbum(albumId);
}
}
public static List<MusicTrack> getFirstAlbumTrack(Context context, String albumId) {
try (var db = connectToDb(context)) {
return db.getFirstAlbumTrack(albumId);
}
}
public static List<MusicTrack> getPlaylist(Context context) {
try (var db = connectToDb(context)) {
return db.getPlaylist();
}
}
@@ -55,12 +73,6 @@ public class MusicCacheDbDelegate {
}
}
public static boolean isEmpty(Context context) {
try (var db = connectToDb(context)) {
return db.isDatabaseEmpty();
}
}
public static boolean isCachedTrack(Context context, String trackId) {
try (var db = connectToDb(context)) {
return db.isCachedTrack(trackId);
@@ -2,21 +2,15 @@ package ru.vtosters.lite.music.cache;
import android.os.RemoteException;
import android.text.TextUtils;
import com.vk.dto.music.MusicTrack;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AccountManagerUtils;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
import java.util.List;
public class MusicCacheImpl {
public static void addTrack(MusicTrack track) {
@@ -26,7 +20,7 @@ public class MusicCacheImpl {
track.I != null ? track.I.getId() + "" : "-1",
track.f,
!TextUtils.isEmpty(track.g) ? track.g : "",
MusicTrackUtils.getArtists(track),
M3UDownloader.getArtists(track),
track.I != null ? track.I.getTitle() : "",
track.K,
track.h,
@@ -38,33 +32,25 @@ public class MusicCacheImpl {
MusicCacheStorageUtils.removeTrackDirById(trackId);
}
public static List<MusicTrack> getAllOwnTracks() {
return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1");
public static List<MusicTrack> getAllTracks() {
return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext());
}
public static List<MusicTrack> getPlaylistSongs(String owner_id, String playlist_id) {
return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), owner_id + "_" + playlist_id); // get songs from playlist
public static List<MusicTrack> getAlbumById(String albumId) {
return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId);
}
public static Playlist getPlaylist(String playlist_id, String owner_id) {
return getPlaylistById(owner_id + "_" + playlist_id); // get playlist from db
public static List<MusicTrack> getFirstAlbumTrack(String albumId) {
return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId);
}
public static List<Playlist> getPlaylists() {
return PlaylistCacheDbDelegate.getAllPlaylists(AndroidUtils.getGlobalContext()); // get all cached playlists
}
public static Playlist getPlaylistById(String query) {
return PlaylistCacheDbDelegate.getPlaylistById(AndroidUtils.getGlobalContext(), query); // get playlist from db
}
public static boolean hasPlaylist() {
return !PlaylistCacheDbDelegate.isPlaylistsDbEmpty(AndroidUtils.getGlobalContext()); // has playlists or not
public static List<MusicTrack> getPlaylist() {
return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext());
}
public static long getTracksCount() {
return !LibVKXClient.isIntegrationEnabled()
? PlaylistCacheDbDelegate.getTracksCountInPlaylist(AndroidUtils.getGlobalContext(),AccountManagerUtils.getUserId() + "_-1")
? MusicCacheDbDelegate.getTracksCount(AndroidUtils.getGlobalContext())
: LibVKXClient.getInstance().runOnServiceSync(
new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
@Override
@@ -72,7 +58,7 @@ public class MusicCacheImpl {
try {
return (long) service.getCache().size();
} catch (RemoteException e) {
e.fillInStackTrace();
e.printStackTrace();
return defaultValue();
}
}
@@ -89,12 +75,11 @@ public class MusicCacheImpl {
}
public static boolean isEmpty() {
return !LibVKXClient.isIntegrationEnabled() && MusicCacheDbDelegate.isEmpty(AndroidUtils.getGlobalContext());
return !LibVKXClient.isIntegrationEnabled() && getTracksCount() == 0L;
}
public static void clear() {
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
PlaylistCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
MusicCacheStorageUtils.clear();
}
}
}
@@ -8,14 +8,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.Nullable;
import com.vk.dto.music.AlbumLink;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Thumb;
import org.json.JSONObject;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
import java.lang.annotation.Retention;
@@ -24,12 +24,6 @@ import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
/**
* <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
* first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper} </p>
@@ -123,14 +117,12 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
vals.put(Constants.COLUMN_ALBUM_ID, track.I != null ? track.I.getId() + "" : "-1");
vals.put(Constants.COLUMN_TITLE, track.f);
vals.put(Constants.COLUMN_SUBTITLE, track.g);
vals.put(Constants.COLUMN_ARTIST, MusicTrackUtils.getArtists(track));
vals.put(Constants.COLUMN_ARTIST, M3UDownloader.getArtists(track));
vals.put(Constants.COLUMN_ALBUM_TITLE, track.I != null ? track.I.getTitle() : "");
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(track.K, true));
vals.put(Constants.COLUMN_DURATION, track.h);
vals.put(Constants.COLUMN_HAS_ARTWORK, track.I != null && track.I.u1() != null);
db.insert(Constants.TABLE_NAME, null, vals);
PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1", track.y1());
}
}
}
@@ -174,30 +166,18 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
}
public boolean isDatabaseEmpty() {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT EXISTS(SELECT 1 FROM " + Constants.TABLE_NAME + " LIMIT 1)", null);
cursor.moveToFirst();
boolean isEmpty = cursor.getInt(0) == 0;
cursor.close();
return isEmpty;
}
public MusicTrack getTrackById(String trackId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId}, null, null, null);
MusicTrack track = null;
if (cursor.moveToFirst()) {
track = fromCursor(cursor);
}
cursor.close();
return track;
}
public List<MusicTrack> getAllTracks() {
return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null));
}
public List<MusicTrack> getAlbum(String albumId) {
return getAlbumTracks(albumId, null);
}
public List<MusicTrack> getFirstAlbumTrack(String albumId) {
return getAlbumTracks(albumId, "0,1");
}
private List<MusicTrack> getAlbumTracks(String albumId, @Nullable String limit) {
return getTracksWithCursor(getReadableDatabase().query(
Constants.TABLE_NAME,
@@ -236,7 +216,7 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
@Retention(RetentionPolicy.SOURCE)
public @interface Constants {
int DV_VERSION = 0x3;
int DV_VERSION = 0x2;
String DB_NAME = "vt_lite_cache.db";
String TABLE_NAME = "tracks";
//columns
@@ -1,306 +0,0 @@
package ru.vtosters.lite.music.cache.db;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.utils.music.MusicCacheStorageUtils.*;
public class PlaylistCacheDb extends SQLiteOpenHelper implements AutoCloseable {
public PlaylistCacheDb(Context context) {
super(context, Constants.DB_NAME, null, Constants.DV_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(Constants.CREATE_QUERY);
db.execSQL(Constants.CREATE_PLAYLIST_TRACKS_QUERY);
}
private static Playlist fromCursor(Cursor cur) throws JSONException {
@SuppressLint("Range")
String photoString = cur.getString(cur.getColumnIndex(Constants.COLUMN_PHOTO));
JSONObject photo = null;
try {
photo = new JSONObject(photoString);
} catch (JSONException e) {
// ok and
}
@SuppressLint("Range")
JSONObject playlist = PlaylistHelper.generatePlaylist(
cur.getInt(cur.getColumnIndex(Constants.COLUMN_ID)),
cur.getInt(cur.getColumnIndex(Constants.COLUMN_OWNER_ID)),
Boolean.parseBoolean(cur.getString(cur.getColumnIndex(Constants.COLUMN_IS_EXPLICIT))),
cur.getString(cur.getColumnIndex(Constants.COLUMN_TITLE)),
cur.getString(cur.getColumnIndex(Constants.COLUMN_DESCRIPTION)),
photo);
Log.d("Playlist", "generated " + Playlist.U.a(playlist).v1());
return Playlist.U.a(playlist);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(Constants.DROP_QUERY);
onCreate(db);
}
public void addPlaylist(int id, int ownerId, boolean isExplicit, String title, String description, JSONObject photo) {
ContentValues values = new ContentValues();
values.put(Constants.COLUMN_ID, id);
values.put(Constants.COLUMN_OWNER_ID, ownerId);
values.put(Constants.COLUMN_IS_EXPLICIT, isExplicit);
values.put(Constants.COLUMN_TITLE, title);
values.put(Constants.COLUMN_DESCRIPTION, description);
values.put(Constants.COLUMN_PHOTO, String.valueOf(photo));
getWritableDatabase().insert(Constants.TABLE_NAME, null, values);
}
private void deletePlaylist(int id, int ownerId) {
getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)});
}
public boolean isCachedPlaylist(int id, int ownerId) {
Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, new String[]{Constants.COLUMN_ID}, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null);
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}
public long getPlaylistsCount() {
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
}
public void deletePlaylistWithTracks(String playlistId) {
SQLiteDatabase db = getWritableDatabase();
String[] parts = playlistId.split("_");
int id = Integer.parseInt(parts[1]);
int ownerId = Integer.parseInt(parts[0]);
// Get all tracks in the playlist
List<MusicTrack> tracks = getTracksInPlaylist(playlistId);
// Delete the playlist
deletePlaylist(id, ownerId);
// Delete each track from the playlist_tracks table
for (MusicTrack track : tracks) {
String trackId = track.y1();
removeTrackFromPlaylist(playlistId, trackId);
removePlaylistThumb(playlistId);
if (!isTrackInAnyOtherPlaylist(trackId, playlistId)) {
MusicCacheImpl.removeTrack(trackId);
Log.d("Playlist", "removed " + trackId);
}
}
// Delete all entries from the playlist_tracks table for this playlist
db.delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ?", new String[]{playlistId});
}
private boolean isTrackInAnyOtherPlaylist(String trackId, String playlistId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(
Constants.TABLE_PLAYLIST_TRACKS,
null,
Constants.COLUMN_TRACK_ID + " = ? AND " + Constants.COLUMN_PLAYLIST_ID + " != ?",
new String[]{trackId, playlistId},
null, null, null
);
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}
public List<Playlist> getAllPlaylists() {
Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null);
List<Playlist> playlists = getPlaylistsWithCursor(cursor);
cursor.close();
return playlists;
}
public Playlist getPlaylistById(int id, int ownerId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null);
Playlist playlist = null;
if (cursor.moveToFirst()) {
try {
playlist = fromCursor(cursor);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
cursor.close();
return playlist;
}
private List<Playlist> getPlaylistsWithCursor(Cursor cur) {
List<Playlist> playlists = new ArrayList<>();
while (cur.moveToNext()) {
try {
playlists.add(fromCursor(cur));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
return playlists;
}
public long getTracksCountInPlaylist(String playlistId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(
Constants.TABLE_PLAYLIST_TRACKS,
new String[]{Constants.COLUMN_TRACK_ID},
Constants.COLUMN_PLAYLIST_ID + " = ?",
new String[]{playlistId},
null, null, null
);
long count = cursor.getCount();
cursor.close();
return count;
}
public List<String> getAllPlaylistIds() {
SQLiteDatabase db = getReadableDatabase();
List<String> playlistIds = new ArrayList<>();
Cursor cursor = db.query(
Constants.TABLE_NAME,
new String[]{Constants.COLUMN_ID, Constants.COLUMN_OWNER_ID},
null, null, null, null, null
);
if (cursor.moveToFirst()) {
do {
String playlistId = cursor.getString(cursor.getColumnIndexOrThrow(Constants.COLUMN_OWNER_ID)) + "_" + cursor.getString(cursor.getColumnIndexOrThrow(Constants.COLUMN_ID));
playlistIds.add(playlistId);
} while (cursor.moveToNext());
}
cursor.close();
return playlistIds;
}
public boolean isPlaylistsDbEmpty() {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT EXISTS(SELECT 1 FROM " + Constants.TABLE_NAME + " LIMIT 1)", null);
cursor.moveToFirst();
boolean isEmpty = cursor.getInt(0) == 0;
cursor.close();
return isEmpty;
}
public void addTrackToPlaylist(String playlistId, String trackId) {
ContentValues values = new ContentValues();
values.put(Constants.COLUMN_PLAYLIST_ID, playlistId);
values.put(Constants.COLUMN_TRACK_ID, trackId);
getWritableDatabase().insert(Constants.TABLE_PLAYLIST_TRACKS, null, values);
}
public void removeTrackFromPlaylist(String playlistId, String trackId) {
getWritableDatabase().delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ? AND " + Constants.COLUMN_TRACK_ID + " = ?", new String[]{playlistId, trackId});
}
public boolean isPlaylistEmpty(String playlistId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT EXISTS(SELECT 1 FROM " + Constants.TABLE_PLAYLIST_TRACKS + " WHERE " + Constants.COLUMN_PLAYLIST_ID + " = ? LIMIT 1)", new String[]{playlistId});
cursor.moveToFirst();
boolean isEmpty = cursor.getInt(0) == 0;
cursor.close();
return isEmpty;
}
public List<MusicTrack> getTracksInPlaylist(String playlistId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(
Constants.TABLE_PLAYLIST_TRACKS,
new String[]{Constants.COLUMN_TRACK_ID},
Constants.COLUMN_PLAYLIST_ID + " = ?",
new String[]{playlistId},
null, null, null
);
List<MusicTrack> tracks = new ArrayList<>();
while (cursor.moveToNext()) {
@SuppressLint("Range")
String trackId = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_TRACK_ID));
MusicTrack track = TracklistHelper.getTrack(trackId);
Log.d("Playlist", "add " + trackId + " to playlist " + playlistId);
if (track != null) {
tracks.add(track);
}
}
cursor.close();
return tracks;
}
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.setForeignKeyConstraintsEnabled(true);
}
@Retention(RetentionPolicy.SOURCE)
public @interface Constants {
int DV_VERSION = 0x2;
String DB_NAME = "vt_lite_cache_playlists.db";
String TABLE_NAME = "playlists";
//columns
String COLUMN_ID = "id";
String COLUMN_OWNER_ID = "owner_id";
String COLUMN_IS_EXPLICIT = "is_explicit";
String COLUMN_TITLE = "title";
String COLUMN_DESCRIPTION = "description";
String COLUMN_PHOTO = "photo";
// tracks
String COLUMN_TRACK_ID = "track_id";
String TABLE_PLAYLIST_TRACKS = "playlist_tracks";
String COLUMN_PLAYLIST_ID = "playlist_id";
//queries
String CREATE_QUERY = "create table " + TABLE_NAME
+ "("
+ COLUMN_ID + " INTEGER PRIMARY KEY autoincrement,"
+ COLUMN_OWNER_ID + " INTEGER not null,"
+ COLUMN_IS_EXPLICIT + " TEXT not null,"
+ COLUMN_TITLE + " TEXT not null,"
+ COLUMN_DESCRIPTION + " TEXT not null,"
+ COLUMN_PHOTO + " TEXT not null"
+ ")";
String CREATE_PLAYLIST_TRACKS_QUERY = "create table " + TABLE_PLAYLIST_TRACKS + "("
+ COLUMN_PLAYLIST_ID + " TEXT not null,"
+ COLUMN_TRACK_ID + " TEXT not null"
+ ")";
String DROP_QUERY = "drop table if exists " + TABLE_NAME;
}
}
@@ -1,149 +0,0 @@
package ru.vtosters.lite.music.cache.delegate;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.db.PlaylistCacheDb;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import java.util.List;
public class PlaylistCacheDbDelegate {
static synchronized private PlaylistCacheDb connectToDb(Context context) {
synchronized (PlaylistCacheDb.class) {
return new PlaylistCacheDb(context);
}
}
public static void addPlaylist(Context context, Playlist playlist) {
try (var db = connectToDb(context)) {
db.addPlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, generatePhotoJSON(playlist));
}
}
public static JSONObject generatePhotoJSON(Playlist playlist) {
if (PlaylistUtils.getThumb(playlist) != null) {
JSONObject photo = new JSONObject();
String playlistId = playlist.v1();
try {
photo.put("height", 300);
photo.put("width", 300);
addPhotoSizeToJSON(photo, playlistId, 600, "photo_600");
addPhotoSizeToJSON(photo, playlistId, 34, "photo_34");
addPhotoSizeToJSON(photo, playlistId, 1200, "photo_1200");
addPhotoSizeToJSON(photo, playlistId, 68, "photo_68");
addPhotoSizeToJSON(photo, playlistId, 135, "photo_135");
addPhotoSizeToJSON(photo, playlistId, 300, "photo_300");
addPhotoSizeToJSON(photo, playlistId, 270, "photo_270");
return photo;
} catch (JSONException e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
private static void addPhotoSizeToJSON(JSONObject photo, String playlistId, int size, String key) throws JSONException {
photo.put(key, Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString());
Log.d("Playlist", "thumb link " + Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString());
}
public static void deletePlaylist(Context context, String playlistId) {
try (var db = connectToDb(context)) {
db.deletePlaylistWithTracks(playlistId);
}
}
public static long getTracksCountInPlaylist(Context context, String playlistId) {
try (var db = connectToDb(context)) {
return db.getTracksCountInPlaylist(playlistId);
}
}
public static boolean isPlaylistsDbEmpty(Context context) {
try (var db = connectToDb(context)) {
return db.isPlaylistsDbEmpty();
}
}
public static void removeAllPlaylists(Context context) {
List<String> playlistIds = getAllPlaylistIds(context);
for (String playlistId : playlistIds) {
deletePlaylist(context, playlistId);
}
}
public static List<String> getAllPlaylistIds(Context context) {
try (var db = connectToDb(context)) {
return db.getAllPlaylistIds();
}
}
public static boolean isCachedPlaylist(Context context, String id) {
try (var db = connectToDb(context)) {
String[] parts = id.split("_");
int playlistId = Integer.parseInt(parts[1]);
int ownerId = Integer.parseInt(parts[0]);
return db.isCachedPlaylist(playlistId, ownerId);
}
}
public static long getPlaylistsCount(Context context) {
try (var db = connectToDb(context)) {
return db.getPlaylistsCount();
}
}
public static List<Playlist> getAllPlaylists(Context context) {
try (var db = connectToDb(context)) {
return db.getAllPlaylists();
}
}
public static Playlist getPlaylistById(Context context, String playlistId) {
try (var db = connectToDb(context)) {
String[] parts = playlistId.split("_");
int id = Integer.parseInt(parts[1]);
int ownerId = Integer.parseInt(parts[0]);
return db.getPlaylistById(id, ownerId);
}
}
public static void addTrackToPlaylist(Context context, String playlistId, String trackId) {
try (var db = connectToDb(context)) {
db.addTrackToPlaylist(playlistId, trackId);
}
}
public static void removeTrackFromPlaylist(Context context, String playlistId, String trackId) {
try (var db = connectToDb(context)) {
db.removeTrackFromPlaylist(playlistId, trackId);
}
}
public static List<MusicTrack> getTracksInPlaylist(Context context, String playlistId) {
try (var db = connectToDb(context)) {
return db.getTracksInPlaylist(playlistId);
}
}
public static boolean isPlaylistEmpty(Context context, String playlistId) {
try (var db = connectToDb(context)) {
return db.isPlaylistEmpty(playlistId);
}
}
public static void drop(Context context) {
context.deleteDatabase(PlaylistCacheDb.Constants.DB_NAME);
}
}
@@ -1,79 +1,93 @@
package ru.vtosters.lite.music.cache.helpers;
import android.util.Log;
import android.net.Uri;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
public class PlaylistHelper {
public static Playlist createCachedPlaylistMetadata() {
try {
return new Playlist(getCachedSongsPlaylist());
return new Playlist(getPlaylist());
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
public static JSONArray addCachedPlaylists(JSONArray jsonArray) {
public static Playlist createCachedPlaylistMetadata(String id) {
var track = TracklistHelper.getTracksOne(id).get(0);
var albumLink = track.I;
var thumb = albumLink.u1();
return createAlbum(
thumb != null ? thumb.h(600) : "",
albumLink.getTitle(),
M3UDownloader.getArtists(track),
track.y1(),
albumLink.getId()
);
}
public static Playlist createAlbum(String photo, String title, String artist, String trackId, int albumId) {
try {
if (!PlaylistCacheDbDelegate.isPlaylistEmpty(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1")) {
jsonArray.put(getCachedSongsPlaylist());
}
if (MusicCacheImpl.hasPlaylist()) {
for (Playlist playlist : MusicCacheImpl.getPlaylists()) {
jsonArray.put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist)));
Log.d("PlaylistHelper", "Playlist cache added: " + playlist.a + " " + playlist.b + " " + playlist.C + " " + playlist.g + " " + playlist.B);
}
}
var jPhoto = new JSONObject();
if (!photo.isEmpty())
jPhoto.put("height", 600)
.put("width", 600)
.putOpt("photo_300", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 300)).toString())
.putOpt("photo_600", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 600)).toString());
var obj = new JSONObject()
.put("id", -2)
.put("owner_id", albumId)
.put("type", 1)
.put("album_type", "main_only")
.put("title", title)
.put("description", albumId + "/ " + AndroidUtils.getString(R.string.cached_album_warning))
.put("main_artists", new JSONArray()
.put(new JSONObject().put("name", artist)
.put("id", "-1")
.put("domain", "-1")))
.putOpt("photo", jPhoto)
.put("count", 0);
return new Playlist(obj);
} catch (JSONException e) {
e.fillInStackTrace();
e.printStackTrace();
return null;
}
return jsonArray;
}
public static JSONObject generatePlaylist(int id, int owner_id, boolean is_explicit, String title, String description, JSONObject photo) throws JSONException {
JSONObject playlist = new JSONObject()
.put("id", id)
.put("owner_id", owner_id)
.put("type", 0)
.put("album_type", "playlist")
.put("is_explicit", is_explicit)
.put("title", title)
.put("description", description)
.put("access_key", "cache")
.put("genres", new JSONArray())
.put("is_following", false)
.put("followers", 0)
.put("plays", 0)
.put("create_time", 0)
.put("update_time", 0)
.put("subtitle", "")
.put("meta", new JSONObject()
.put("view", "compact"))
.put("count", 0);
if (photo != null) {
playlist.put("photo", photo);
public static List<Playlist> getAlbumPlaylists() {
List<Playlist> list = new ArrayList<>();
for (var track : MusicCacheImpl.getPlaylist()) {
var albumLink = track.I;
var thumb = albumLink.u1();
list.add(createAlbum(
thumb != null ? thumb.h(600) : "",
albumLink.getTitle(),
M3UDownloader.getArtists(track),
track.y1(),
albumLink.getId()
));
}
return playlist;
return list;
}
public static JSONObject getCachedSongsPlaylist() throws JSONException {
public static JSONObject getPlaylist() throws JSONException {
return new JSONObject()
.put("id", -1)
.put("owner_id", AccountManagerUtils.getUserId())
.put("owner_id", getUserId())
.put("type", 0)
.put("album_type", "playlist")
.put("title", AndroidUtils.getString(R.string.cached_tracks_title))
@@ -101,20 +115,6 @@ public class PlaylistHelper {
.put("count", 0);
}
public static JSONArray getCachedPlaylistsIds() {
JSONArray arr = new JSONArray();
if (NetworkUtils.isNetworkConnected() && !PlaylistCacheDbDelegate.isPlaylistEmpty(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1")) {
arr.put(AccountManagerUtils.getUserId() + "_-1");
}
for (String playlist : PlaylistCacheDbDelegate.getAllPlaylistIds(AndroidUtils.getGlobalContext())) {
arr.put(playlist);
}
return arr;
}
public static JSONObject getCatalogPlaylist() throws JSONException {
return new JSONObject()
.put("id", "cache")
@@ -122,17 +122,18 @@ public class PlaylistHelper {
.put("layout", new JSONObject()
.put("name", "large_slider")
.put("is_editable", 0)
.put("owner_id", AccountManagerUtils.getUserId()))
.put("playlists_ids", getCachedPlaylistsIds());
.put("owner_id", getUserId()))
.put("playlists_ids", new JSONArray()
.put(getUserId() + "_-1"));
}
public static JSONObject getCatalogHeader(String text) throws JSONException {
public static JSONObject getCatalogHeader() throws JSONException {
return new JSONObject()
.put("id", "cache")
.put("data_type", "none")
.put("layout", new JSONObject()
.put("name", "header")
.put("title", text));
.put("title", AndroidUtils.getString(R.string.cached_tracks_title)));
}
public static JSONObject getCatalogSeparator() throws JSONException {
@@ -142,4 +143,4 @@ public class PlaylistHelper {
.put("layout", new JSONObject()
.put("name", "separator"));
}
}
}
@@ -8,8 +8,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
@@ -24,19 +22,24 @@ public class TracklistHelper {
}
public static List<MusicTrack> getTracks() {
List<MusicTrack> tracks = MusicCacheImpl.getAllOwnTracks();
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
if (doNotinvertOrder) {
if (!doNotinvertOrder) {
Collections.reverse(tracks);
}
return tracks;
}
public static MusicTrack getTrack(String id) {
return MusicCacheDbDelegate.getTrackById(AndroidUtils.getGlobalContext(), id);
public static List<MusicTrack> getTracks(String id) {
return MusicCacheImpl.getAlbumById(id);
}
public static List<MusicTrack> getTracksOne(String id) {
return MusicCacheImpl.getFirstAlbumTrack(id);
}
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
@@ -1,12 +1,9 @@
package ru.vtosters.lite.music.callback;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.vtosters.lite.R;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.utils.AndroidUtils;
@@ -43,12 +40,12 @@ public class MusicCallbackBuilder {
} catch (UnsatisfiedLinkError e) {
Log.e("AudioDownloader", "native libs");
Log.e("AudioDownloader", e.getMessage());
onFailure(e);
onFailure();
}
}
@Override
public void onFailure(Throwable e) {
public void onFailure() {
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
notificationManager.notify(notificationId, notification.build());
}
@@ -77,11 +74,10 @@ public class MusicCallbackBuilder {
public void onSuccess() {
notification.setContentText(AndroidUtils.getString(R.string.player_download_finished));
notification.setProgress(0, 0, false);
notification.setSmallIcon(android.R.drawable.stat_sys_download_done);
}
@Override
public void onFailure(Throwable e) {
public void onFailure() {
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
notificationManager.notify(notificationId, notification.build());
}
@@ -53,9 +53,7 @@ public class PlaylistConverter {
var request = new Request.a()
.b(requestUrl)
.a("Accept-Encoding", "gzip")
.a("User-Agent", Network.l.c().a())
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.a();
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
var response = CompletableFuture.supplyAsync(() -> {
@@ -0,0 +1,45 @@
package ru.vtosters.lite.music.converter.ts;
import com.arthenica.ffmpegkit.FFmpegKit;
import com.vk.dto.music.Artist;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.IOUtils;
import java.io.File;
import java.util.stream.Collectors;
public class FFMpeg {
/**
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
* @param out Output .mp3 file
* @param track Track to be converted
* @return True if success, else false
*/
public static void convert(File in, String out, MusicTrack track) {
// convert .ts file to .mp3 file via ffmpeg
var cmd = " -y -i \"" + in.getAbsolutePath() + '"'
+ " -map 0 -dn"
+ " -loglevel error"
+ " -hide_banner"
+ " -write_id3v2 1"
+ setMetadata(track)
+ " -c copy \"" + out + '"';
var ses = FFmpegKit.execute(cmd);
if (ses.getReturnCode().isValueCancel()) throw new RuntimeException(ses.getLogsAsString());
}
private static String setMetadata(MusicTrack track) {
return Preferences.getBoolValue("setMetaData", true) ? " -metadata title=\"" + IOUtils.getValidFileName(M3UDownloader.getTitle(track)) + '"'
+ (track.L == null || track.I == null
? " -metadata artist=\"" + normalizeMetadata(track.C) + '"'
: " -metadata artist=\"" + normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))) + '"'
+ " -metadata album=\"" + normalizeMetadata(track.I.getTitle()) + '"') : "";
}
private static String normalizeMetadata(String in) {
return in.replaceAll("[\\\\/:*?\"<>|]", "");
}
}
@@ -1,79 +0,0 @@
package ru.vtosters.lite.music.converter.ts;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class MpegDemuxer {
public static void convert(byte[] inBytes, String out) throws IOException {
try (InputStream inStream = new ByteArrayInputStream(inBytes)) {
convert(inStream, out);
}
}
public static void convert(InputStream in, String out) throws IOException {
int packetSize = 188;
int desiredPid = 256;
var buffer = new byte[packetSize];
var packet = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN);
try (var fis = new BufferedInputStream(in); var fos = new BufferedOutputStream(new FileOutputStream(out))) {
while (fis.read(buffer) != -1) {
packet.clear();
int firstByte = packet.get() & 0xFF;
int secondByte = packet.get() & 0xFF;
int thirdByte = packet.get() & 0xFF;
int fourthByte = packet.get() & 0xFF;
int syncByte = firstByte;
int transportErrorIndicator = (secondByte & 0x80) >> 7;
int payloadUnitStartIndicator = (secondByte & 0x40) >> 6;
int pid = ((secondByte & 0x1F) << 8) | (thirdByte & 0xFF);
int adaptationFieldControl = (fourthByte & 0x30) >> 4;
if (syncByte != 0x47) {
Log.d("MpegDemuxer", "Invalid sync byte: 0x" + Integer.toHexString(syncByte));
continue;
}
if (transportErrorIndicator == 1) {
Log.d("MpegDemuxer", "Transport error indicator is set");
continue;
}
if (adaptationFieldControl == 2 || adaptationFieldControl == 3) {
int adaptationFieldLength = packet.get() & 0xFF;
packet.position(packet.position() + adaptationFieldLength);
}
if (pid == desiredPid) {
if (payloadUnitStartIndicator == 1) {
packet.position(packet.position() + 4);
int pesPacketLength = packet.getShort() & 0xFFFF;
if (pesPacketLength != 0) {
packet.position(packet.position() + 2);
int pesHeaderDataLength = packet.get() & 0xFF;
packet.position(packet.position() + pesHeaderDataLength);
} else {
Log.v("MpegDemuxer", "PES packet length is zero, that shouldn't happen in audio stream");
}
}
fos.write(packet.array(), packet.position(), packetSize - packet.position());
}
}
}
}
}
@@ -0,0 +1,70 @@
package ru.vtosters.lite.music.converter.ts;
import android.os.Build;
import ru.vtosters.lite.utils.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Comparator;
public class TSMerger {
/**
* Merge multiple .ts files into one .ts file
*
* @param in Directory with .ts files
*/
public static void merge(File in, File out) {
File[] tsesList = in.listFiles();
if (tsesList == null) {
return;
}
ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
sortTsesList(tsesList);
for (File file : tsesList) {
if (file.getName().endsWith(".ts")) {
try {
mergedByteArrayStream.write(IOUtils.readFully(file));
} catch (IOException e) {
return;
}
}
}
writeOutputFile(out, mergedByteArrayStream);
}
private static void sortTsesList(File[] tsesList) {
Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
}
private static int getFileIndex(File file) {
String fileName = file.getName();
int dotIndex = fileName.indexOf('.', 1);
return Integer.parseInt(fileName.substring(0, dotIndex));
}
private static void writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
byte[] bytes = mergedByteArrayStream.toByteArray();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.write(out.toPath(), bytes);
} catch (IOException ignored) {
// ignored
}
} else {
try (FileOutputStream fos = new FileOutputStream(out)) {
fos.write(bytes);
} catch (IOException ignored) {
// ignored
}
}
}
}
@@ -47,9 +47,8 @@ public class AudioGet {
var request = new Request.a()
.b(requestUrl)
.a("Accept-Encoding", "gzip")
.a("User-Agent", Network.l.c().a())
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.a();
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
var response = CompletableFuture.supplyAsync(() -> {
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
@@ -1,65 +0,0 @@
package ru.vtosters.lite.music.downloader;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
public final class CachedDownloader implements ITrackDownloader {
private final ITrackDownloader origin;
public CachedDownloader(ITrackDownloader origin) {
this.origin = origin;
}
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
origin.download(track, new Callback() {
@Override
public void onProgress(int progress) {
callback.onProgress(progress);
}
@Override
public void onSuccess() {
new ThumbnailTrackDownloader().download(track, new Callback() {
@Override public void onProgress(int progress) {
}
@Override public void onSuccess() {
}
@Override
public void onFailure(Throwable e) {
callback.onFailure(e);
}
@Override public void onSizeReceived(long size, long header) {
}
}, null);
PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), playlist.v1(), track.y1());
MusicCacheImpl.addTrack(track);
callback.onSuccess();
}
@Override
public void onFailure(Throwable e) {
callback.onFailure(e);
}
@Override
public void onSizeReceived(long size, long header) {
callback.onSizeReceived(size, header);
}
}, playlist);
}
}
@@ -1,56 +0,0 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.mpatric.mp3agic.ID3v23Tag;
import com.mpatric.mp3agic.InvalidDataException;
import com.mpatric.mp3agic.Mp3File;
import com.mpatric.mp3agic.NotSupportedException;
import com.mpatric.mp3agic.UnsupportedTagException;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Artist;
import java.io.File;
import java.io.IOException;
import java.util.stream.Collectors;
import ru.vtosters.hooks.other.Preferences;
public class ID3Tagger {
public static void tag(File file, MusicTrack track) {
try {
// https://github.com/mpatric/mp3agic/issues/69#issuecomment-57897874
// TODO: get rid of file duplication
var oldFile = new File(file.getAbsolutePath().replace(".mp3", "_old.mp3"));
if (!file.renameTo(oldFile)) {
Log.e("ID3Tagger", "Failed to rename file");
return;
}
var mp3File = new Mp3File(oldFile);
if (mp3File.hasId3v2Tag()) {
var id3v2Tag = new ID3v23Tag();
if (track.L == null || track.I == null) {
id3v2Tag.setArtist(track.C);
} else {
var artists = track.L.stream().map(Artist::w1).collect(Collectors.joining(Preferences.metadataSeparator()));
id3v2Tag.setArtist(artists);
id3v2Tag.setAlbum(track.I.getTitle());
}
id3v2Tag.setTitle(Mp3Downloader.getTitle(track));
mp3File.setId3v2Tag(id3v2Tag);
mp3File.save(file.getAbsolutePath());
if (!oldFile.delete()) {
Log.e("ID3Tagger", "Failed to delete file");
}
}
} catch (IOException | UnsupportedTagException | InvalidDataException |
NotSupportedException e) {
Log.e("ID3Tagger", "Failed to tag file", e);
}
}
}
@@ -0,0 +1,193 @@
package ru.vtosters.lite.music.downloader;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
import com.vk.dto.music.Artist;
import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import java8.util.concurrent.CompletionException;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.converter.ts.FFMpeg;
import ru.vtosters.lite.music.converter.ts.TSMerger;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class M3UDownloader
implements ITrackDownloader {
public static M3UDownloader getInstance() {
return Holder.INSTANCE;
}
static void parse(
File outDir,
Callback callback,
MusicTrack track,
boolean cache)
throws IOException {
var parser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
var segments = ((f) parser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
var progress = new AtomicInteger();
callback.onProgress(5);
var resultTs = getResultTsFile(track);
var resultMp3 = getResultMp3File(outDir, cache, track);
try {
CompletableFuture.allOf(createFutures(baseUri, segments, progress, callback, track))
.thenRun(() -> {
if (cache) {
try {
ThumbnailDownloader.downloadThumbnails(track);
} catch (IOException e) {
throw new RuntimeException("Failed to download thumbs", e);
}
}
})
.thenRun(() -> {
try {
TSMerger.merge(getTsesDir(track), resultTs);
} catch (Throwable e) {
throw new RuntimeException("Failed to merge ts files", e);
}
})
.thenRun(() -> {
try {
FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track);
} catch (Throwable e) {
throw new RuntimeException("FFmpeg error", e);
}
})
.thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
.thenRun(() -> {
if (cache) {
MusicCacheImpl.addTrack(track);
}
})
.whenComplete((unused, tr) -> {
if (tr != null) {
IOUtils.deleteRecursive(MusicCacheStorageUtils.getTrackDirById(track.y1()));
callback.onFailure();
} else {
callback.onSuccess();
}
IOUtils.deleteRecursive(getTsesDir(track));
})
.join();
} catch (CompletionException e) {
Log.e("M3UDownloader", "Failed to download track", e.getCause());
}
}
@SuppressWarnings("rawtypes")
static CompletableFuture[] createFutures(
String baseUri,
List<a> segments,
AtomicInteger progress,
Callback callback,
MusicTrack track) {
var futures = new CompletableFuture[segments.size()];
for (var i = 0; i < segments.size(); ++i) {
var segment = segments.get(i);
var filename = i + ".ts";
futures[i] = CompletableFuture.runAsync(() -> processSegment(baseUri, segment, filename, progress, segments.size(), callback, track));
}
return futures;
}
private static void processSegment(
String baseUri,
a segment,
String filename,
AtomicInteger progress,
int size,
Callback callback,
MusicTrack track) {
try {
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
if (!TextUtils.isEmpty(segment.g)) {
var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
var cipherBytes = segment.h.getBytes();
//if IV doesn't pass, it must be created manually and filled with zeros
if (cipherBytes.length != 16) Arrays.fill((cipherBytes = new byte[16]), (byte) 0x0);
var cipherIv = new IvParameterSpec(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
buff = cipher.doFinal(buff);
}
IOUtils.writeToFile(new File(getTsesDir(track), filename), buff);
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getTitle(MusicTrack track) {
return track.f + (!TextUtils.isEmpty(track.g) ? +'(' + track.g + ')' : "");
}
static File getTsesDir(MusicTrack track) {
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
var tsesDir = new File(filesDir, String.valueOf(track.d));
tsesDir.mkdirs();
return tsesDir;
}
static File getResultTsFile(MusicTrack track) {
return new File(getTsesDir(track), "result.ts");
}
static File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
return new File(outDir, IOUtils.getValidFileName((cache ? "track" : getArtists(track) + " - " + getTitle(track)) + ".mp3"));
}
@Override
public void downloadTrack(
MusicTrack track,
File outDir,
Callback callback,
boolean cache) {
if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
try {
parse(outDir, callback, track, cache);
} catch (IOException e) {
callback.onFailure();
Log.e("M3UDownloader", e + "");
throw new RuntimeException(e);
}
}
// Initialization-on-demand
private static class Holder {
private static final M3UDownloader INSTANCE = new M3UDownloader();
}
public static String getArtists(MusicTrack track) {
return (track.L == null || track.I == null
? normalizeMetadata(track.C)
: normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
}
private static String normalizeMetadata(String in) {
return in.replaceAll("[\\\\/:*?\"<>|]", "");
}
}
@@ -0,0 +1,76 @@
//package ru.vtosters.lite.music;
//
//import android.text.TextUtils;
//import android.util.Log;
//import android.widget.Toast;
//
//import com.vk.dto.music.MusicTrack;
//
//import org.json.JSONException;
//import org.json.JSONObject;
//
//import java.io.File;
//import java.io.IOException;
//
//import okhttp3.Call;
//import okhttp3.Callback;
//import okhttp3.OkHttpClient;
//import okhttp3.Request;
//import okhttp3.Response;
//import ru.vtosters.lite.downloaders.AudioDownloader;
//import ru.vtosters.lite.music.interfaces.ITrackDownloader;
//import ru.vtosters.lite.utils.AndroidUtils;
//
//public class MP3Downloader implements ITrackDownloader {
// private static final String URL_FORMAT = "https://api.vk.com/method/audio.getById?v=5.131&access_token=%s&audios=%s&copyright=";
//
// private static final OkHttpClient client = new OkHttpClient();
//
// public static void downloadMP3(MusicTrack track) {
// var token = AndroidUtils.getPreferences().getString("vk_admin_token", "");
// if (token.isEmpty()) {
// Toast.makeText(AndroidUtils.getGlobalContext(), "Обновите токен VK Admin в настройках", Toast.LENGTH_SHORT).show();
// return;
// }
//
// var request = new Request.a()
// .b(String.format(URL_FORMAT, token, track.y1()))
// .a();
// client.a(request).a(new Callback() {
// @Override
// public void a(Call call, IOException e) {
// Log.d("MP3Downloader", e + "");
// }
//
// @Override
// public void a(Call call, Response response) throws IOException {
// var payload = response.a().g();
// Log.d("MP3Downloader", payload);
// try {
// JSONObject jsonObj = new JSONObject(payload);
// if (!jsonObj.has("response"))
// return;
// JSONObject responseNode = new JSONObject(payload).getJSONArray("response")
// .getJSONObject(0);
// if (!responseNode.has("url"))
// return;
// var url = responseNode.optString("url");
// Log.d("MP3Downloader", url);
// if (TextUtils.isEmpty(url))
// return;
// if (VKM3UParser.isM3U8(url)) {
// AudioDownloader.downloadM3U8(track);
// return;
// }
// } catch (JSONException e) {
// Log.d("MP3Downloader", e + "");
// }
// }
// });
// }
//
// @Override
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
//
// }
//}
@@ -1,111 +0,0 @@
package ru.vtosters.lite.music.downloader;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.converter.ts.MpegDemuxer;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.IOUtils;
public final class Mp3Downloader implements ITrackDownloader {
private final File outputFile;
public Mp3Downloader(File outputFile) {
this.outputFile = outputFile;
}
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
String uri = track.D;
if (Objects.requireNonNull(uri).isEmpty()) {
String msg = "link error: " + track.y1() + ", title: " + Mp3Downloader.getTitle(track);
Log.d("Mp3Downloader", msg);
throw new RuntimeException(msg);
}
if (Objects.requireNonNull(uri).contains("master.m3u8?siren=1")) {
try {
String content = IOUtils.readAllLines(new URL(uri).openStream());
String replacement = content.split("\n")[2].trim();
uri = uri.replace("master.m3u8?siren=1", replacement);
} catch (IOException e) {
callback.onFailure(e);
return;
}
}
var tsParser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(uri));
var baseUri = uri.substring(0, uri.lastIndexOf("/") + 1);
try {
var segments = ((f) tsParser.a(Uri.parse(baseUri), IOUtils.openStream(uri))).o;
byte[] buff = getMergedTs(baseUri, segments, callback);
MpegDemuxer.convert(buff, outputFile.getAbsolutePath());
if (Preferences.getBoolValue("setMetaData", false)) {
ID3Tagger.tag(outputFile, track);
}
callback.onSuccess();
} catch (IOException | GeneralSecurityException e) {
callback.onFailure(e);
}
}
private static byte[] getMergedTs(String baseUri, List<a> segments, Callback callback) throws IOException, GeneralSecurityException {
byte[] total = new byte[0];
int size = segments.size();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
int progress = 0;
for (var segment : segments) {
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
if (!TextUtils.isEmpty(segment.g)) {
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
var cipherBytes = segment.h.getBytes();
//if IV doesn't pass, it must be created manually
if (cipherBytes.length != 16) cipherBytes = new byte[16];
var cipherIv = new IvParameterSpec(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
buff = cipher.doFinal(buff);
}
// copy on write
final int len = total.length, n = buff.length;
byte[] newBytes = Arrays.copyOf(total, len + n);
System.arraycopy(buff, 0, newBytes, len, n);
total = newBytes;
callback.onProgress(10 + Math.round(80.0F * (++progress) / size));
}
return total;
}
static String getTitle(MusicTrack track) {
return track.f + (!TextUtils.isEmpty(track.g) ? '(' + track.g + ')' : "");
}
}
@@ -1,67 +1,63 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.music.interfaces.Callback;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java8.util.concurrent.CompletableFuture;
public class PlaylistDownloader {
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
var outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
else Log.e("PlaylistDownloader", "Directory creation failed");
Callback delegate = new ProgressCallback(callback);
CompletableFuture.allOf(playlist
.stream()
.map(x -> {
Callback.CompletableFutureCallback d = new
Callback.CompletableFutureCallback(delegate);
TrackDownloader.downloadTrack(x, path, d);
return d;
})
.toArray(CompletableFuture[]::new));
var downloadedTracks = new AtomicInteger();
for (var musicTrack : playlist)
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
@Override
public void onProgress(int progress) {
}
@Override
public void onSuccess() {
callback.onProgress(downloadedTracks.incrementAndGet());
}
@Override
public void onFailure() {
callback.onFailure();
}
@Override
public void onSizeReceived(long size, long header) {
}
});
}
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback, Playlist playlistId) {
Callback delegate = new ProgressCallback(callback);
CompletableFuture.allOf(playlist
.stream()
.map(x -> {
Callback.CompletableFutureCallback d = new Callback.CompletableFutureCallback(delegate);
TrackDownloader.cacheTrack(x, d, playlistId);
return d;
})
.toArray(CompletableFuture[]::new));
}
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
var downloadedTracks = new AtomicInteger();
for (var musicTrack : playlist)
TrackDownloader.cacheTrack(musicTrack, new Callback() {
@Override
public void onProgress(int progress) {
}
public static final class ProgressCallback implements Callback {
private final AtomicInteger progress = new AtomicInteger();
private final Callback origin;
@Override
public void onSuccess() {
callback.onProgress(downloadedTracks.incrementAndGet());
}
public ProgressCallback(Callback origin) {
this.origin = origin;
}
@Override
public void onFailure() {
callback.onFailure();
}
@Override public void onProgress(int progress) {}
@Override
public void onSuccess() {
origin.onProgress(progress.incrementAndGet());
}
@Override
public void onFailure(Throwable e) {
origin.onFailure(e);
}
@Override public void onSizeReceived(long size, long header) {}
@Override
public void onSizeReceived(long size, long header) {
}
});
}
}
@@ -0,0 +1,43 @@
package ru.vtosters.lite.music.downloader;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import org.json.JSONArray;
import org.json.JSONObject;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.IOException;
import java.net.URL;
public class ThumbnailDownloader {
public static void downloadThumbnails(MusicTrack track) throws IOException {
JSONObject json = track.J();
JSONObject album = json.optJSONObject("album");
if (album == null) return;
JSONObject thumb = album.optJSONObject("thumb");
if (thumb == null) return;
JSONArray sizes = thumb.optJSONArray("sizes");
String trackId = LibVKXClient.asId(track);
if (sizes == null) {
JSONArray names = thumb.names();
if (names == null) return;
for (int i = 0; i < names.length(); ++i) {
String name = names.optString(i);
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
}
} else {
for (int i = 0; i < sizes.length(); ++i) {
JSONObject size = sizes.optJSONObject(i);
int width = size.optInt("width");
String src = size.optString("src");
downloadThumbnail(src, width, trackId);
}
}
}
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -1,57 +0,0 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import java.io.IOException;
import java.net.URL;
import java.util.List;
public class ThumbnailPlaylistDownloader implements ITrackDownloader {
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
JSONObject json = PlaylistUtils.getThumb(playlist); // get thumbnail photo
if (json == null) {
callback.onFailure(new IOException("No thumbnail found"));
return;
}
try {
List<String> sizes = List.of("600", "1200", "68", "135", "300", "270"); // define the order of sizes to download
JSONArray sizesArray = json.getJSONArray("sizes");
for (String size : sizes) {
for (int i = 0; i < sizesArray.length(); i++) {
JSONObject sizeObj = sizesArray.getJSONObject(i);
if (sizeObj.getInt("width") == Integer.parseInt(size)) {
String url = sizeObj.getString("src");
if (url.isEmpty()) continue;
Log.d("Playlist", "downloading thumb " + url);
downloadThumbnailPlaylist(url, Integer.parseInt(size), playlist.v1());
}
}
}
callback.onSuccess();
} catch (IOException | JSONException e) {
callback.onFailure(e);
throw new RuntimeException(e);
}
}
private static void downloadThumbnailPlaylist(String url, int res, String playlistId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -1,54 +0,0 @@
package ru.vtosters.lite.music.downloader;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URL;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
public final class ThumbnailTrackDownloader implements ITrackDownloader {
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
JSONObject json = track.J();
JSONObject album = json.optJSONObject("album");
if (album == null) return;
JSONObject thumb = album.optJSONObject("thumb");
if (thumb == null) return;
JSONArray sizes = thumb.optJSONArray("sizes");
String trackId = LibVKXClient.asId(track);
try {
if (sizes == null) {
JSONArray names = thumb.names();
if (names == null) return;
for (int i = 0; i < names.length(); ++i) {
String name = names.optString(i);
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
}
} else {
for (int i = 0; i < sizes.length(); ++i) {
JSONObject size = sizes.optJSONObject(i);
int width = size.optInt("width");
String src = size.optString("src");
downloadThumbnail(src, width, trackId);
}
}
callback.onSuccess();
} catch (IOException e) {
callback.onFailure(e);
throw new RuntimeException(e);
}
}
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -1,45 +1,51 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.Playlist;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
public final class TrackDownloader {
private TrackDownloader() { }
import java.io.File;
public class TrackDownloader {
public static void downloadTrack(MusicTrack track, String path, Callback callback) {
File outDir = new File(path);
if (!outDir.exists()) {
if (outDir.mkdir()) {
Log.v("TrackDownloader", "Directory created");
} else {
Log.e("TrackDownloader", "Directory creation failed");
}
}
File outputFile = new File(outDir, IOUtils.getValidFileName(MusicTrackUtils.getArtists(track) + " - " + Mp3Downloader.getTitle(track)) + ".mp3");
VTExecutors.getMusicDownloadExecutor().submit(() -> new Mp3Downloader(outputFile).download(track, callback, null));
}
public static void cacheTrack(MusicTrack track, Callback callback, Playlist playlist) {
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
File outputFile = MusicCacheStorageUtils.getTrackFile(LibVKXClient.asId(track));
download(track, path, callback, M3UDownloader.getInstance(), false);
}
VTExecutors.getMusicDownloadExecutor().submit(() -> new CachedDownloader(new Mp3Downloader(outputFile)).download(track, callback, playlist));
public static void cacheTrack(MusicTrack track, Callback callback) {
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
callback.onSuccess();
return;
} else if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
download(track, path, callback, M3UDownloader.getInstance(), true);
}
private static void download(MusicTrack track, String path, Callback callback, ITrackDownloader downloader, boolean cache) {
File outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdir())
Log.v("TrackDownloader", "Directory created");
else
Log.e("TrackDownloader", "Directory creation failed");
VTExecutors.getMusicDownloadExecutor().execute(
() -> downloader.downloadTrack(track, outDir, callback, cache)
);
}
}
@@ -1,46 +1,11 @@
package ru.vtosters.lite.music.interfaces;
import java8.util.concurrent.CompletableFuture;
public interface Callback {
void onProgress(int progress);
void onSuccess();
void onFailure(Throwable e);
void onFailure();
void onSizeReceived(long size, long header);
class CompletableFutureCallback
extends CompletableFuture<Void>
implements Callback {
private final Callback origin;
public CompletableFutureCallback(Callback origin) {
this.origin = origin;
}
@Override
public void onProgress(int progress) {
origin.onProgress(progress);
}
@Override
public void onSuccess() {
complete(null);
origin.onSuccess();
}
@Override
public void onFailure(Throwable e) {
completeExceptionally(e);
origin.onFailure(e);
}
@Override
public void onSizeReceived(long size, long header) {
origin.onSizeReceived(size, header);
}
}
}
@@ -1,7 +1,8 @@
package ru.vtosters.lite.music.interfaces;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import java.io.File;
/**
* Interface for track downloaders
@@ -11,8 +12,8 @@ public interface ITrackDownloader {
* Download track
*
* @param track Music track
* @param outDir Directory where to download the track
* @param callback Callback to call when download status changes
* @param playlist
*/
void download(MusicTrack track, Callback callback, Playlist playlist);
void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache);
}
@@ -28,7 +28,8 @@ public class ThemesCore {
private static int darken_color;
static {
init();
setExceptions();
setThemedColors(ThemesUtils.getAccentColor());
}
public static void setExceptions() {
@@ -200,9 +201,4 @@ public class ThemesCore {
public static boolean isCachedAccents() {
return cachedAccents;
}
public static void init() {
setExceptions();
setThemedColors(ThemesUtils.getAccentColor());
}
}
}
@@ -10,7 +10,7 @@ import ru.vtosters.lite.utils.NavigatorUtils;
public class InstallGMS {
public static void alert(final Activity activity) {
if (!GmsHook.isAnyServicesInstalled() && !GmsHook.isGmsInstalled() && Preferences.getBoolValue("showAlertGms", true)) {
if (!GmsHook.isAnyServicesInstalled() && Preferences.getBoolValue("showAlertGms", true)) {
new VkAlertDialog.Builder(activity)
.setTitle(activity.getString(R.string.warning))
.setMessage(activity.getString(R.string.gmsissuesumm))
@@ -1,36 +1,36 @@
package ru.vtosters.lite.ui.dialogs;
import android.content.Context;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LayoutUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
public class RoundingSeekbarDialog {
private static final String[] roundOptions = {
"Круглые", "Закругленные", "Квадратные"
};
public static void dialog(Context context) {
LinearLayout mContainer = new LinearLayout(context);
SeekBar seek = new SeekBar(context);
seek.setMax(10);
seek.setKeyProgressIncrement(0);
seek.setProgress(Preferences.getPreferences().getInt("pic_rounding", 0));
seek.setPadding(AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f));
mContainer.addView(seek, LayoutUtils.createLinear(-1, -2));
new VkAlertDialog.Builder(context)
.setTitle(context.getString(R.string.pic_rounding_title))
.setMessage(context.getString(R.string.pic_rounding_info))
.setCancelable(true)
.setItems(roundOptions, (dialog, which) -> {
int val = getRoundingValue(which);
Preferences.getPreferences().edit().putInt("pic_rounding", val).apply();
.setNegativeButton(context.getString(R.string.cancel), null)
.setView(mContainer)
.setPositiveButton(context.getString(R.string.vtl_confirm), (dialog, which) -> {
Preferences.getPreferences().edit().putInt("pic_rounding", seek.getProgress()).commit();
LifecycleUtils.restartApplicationWithTimer();
})
.show();
}
private static int getRoundingValue(int selectedOption) {
int val = 0;
switch (selectedOption) {
case 1 -> val = 10;
case 2 -> val = 1;
}
return val;
}
}
}
@@ -1,41 +1,29 @@
package ru.vtosters.lite.ui.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.preference.Preference;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.ui.SummaryListPreference;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.LastFMScrobbler;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
private static final ExecutorService executor = Executors.newCachedThreadPool();
@SuppressLint("DefaultLocale")
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -56,38 +44,12 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
if (MusicCacheImpl.isEmpty()) {
AndroidUtils.sendToast(requireContext().getString(com.vtosters.lite.R.string.no_cache_error));
} else {
delcache(requireContext(), false);
delcache(requireContext());
}
return true;
}
);
if (MusicCacheImpl.hasPlaylist()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"cached_playlists",
"Кешированные плейлисты",
String.format("Скачано плейлистов: %d", MusicCacheImpl.getPlaylists().size()),
null,
preference -> {
cachedPlaylistsDialog(requireContext());
return true;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"cached_playlists",
"Удалить все скачанные плейлисты",
null,
null,
preference -> {
delcache(requireContext(), true);
return true;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"audio_download",
@@ -113,38 +75,15 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
}
).setEnabled(!MusicCacheImpl.isEmpty());
PreferenceFragmentUtils.addPreference(
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"autocache_params",
"autocache",
getString(com.vtosters.lite.R.string.autocache_title),
getAutocacheSumm(),
getString(com.vtosters.lite.R.string.autocache_summ),
null,
preference -> {
List<String> items = Arrays.asList("Не кешировать", "Только свои", "Все");
ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, items) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(getTextAttr());
return textView;
}
};
int selectedItem = Preferences.getPreferences().getInt("autocaching", 0);
if (selectedItem >= 0 && selectedItem < items.size()) {
adapter.getItem(selectedItem);
}
new VkAlertDialog.Builder(getActivity())
.setAdapter(adapter, (dialog, which) -> {
adapter.getItem(which);
Preferences.getPreferences().edit().putInt("autocaching", which).apply();
findPreference("autocache_params").setSummary(getAutocacheSumm());
dialog.cancel();
})
.show();
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("autocache", (boolean) o).apply();
return true;
}
);
@@ -160,7 +99,6 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("dldir", (boolean) o).apply();
LifecycleUtils.restartApplicationWithTimer();
return true;
}
);
@@ -178,73 +116,6 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"metadataSeparator",
"Разделитель для id3v2 тегов",
null,
null,
preference -> {
LinearLayout linearLayout = new LinearLayout(requireContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
EditText separator = new EditText(requireContext());
separator.setHint("По-умолчанию: ; с пробелом");
separator.setTextColor(ThemesUtils.getTextAttr());
separator.setHintTextColor(ThemesUtils.getSTextAttr());
separator.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
separator.setText(Preferences.metadataSeparator());
linearLayout.addView(separator);
separator.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) separator.getLayoutParams());
margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
separator.setLayoutParams(margin);
new VkAlertDialog.Builder(requireContext())
.setTitle("Разделитель")
.setView(linearLayout)
.setPositiveButton("Сохранить", (dialog, which) -> {
if (separator.getText().toString().isEmpty()) {
AndroidUtils.sendToast("Разделитель не может быть пустым");
return;
}
Preferences.setMetadataSeparator(separator.getText().toString());
findPreference("metadataSeparator").setSummary(separator.getText().toString());
})
.setNegativeButton("Отмена", (dialog, which) -> dialog.cancel())
.show();
return true;
}
);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Рекомендации музыки");
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"sendMusicMetrics",
"Отправлять статистику",
"Статистика прослушиваний необходима для работы рекомендаций и истории прослушивания музыки",
null,
true,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("sendMusicMetrics", (boolean) o).apply();
return true;
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"playStatCatalog",
"Список истории прослушиваний",
"Показывать вкладку истории прослушиваний в музыкальном разделе\n\nОтключение ускорит открытие музыкального раздела при медленном интернете",
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("playStatCatalog", (boolean) o).apply();
return true;
}
).setVisible(Preferences.sendMusicMetrics() && !getBoolValue("useOldAppVer", false));
if (!Preferences.serverFeaturesDisable()) {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Интеграция Genius");
PreferenceFragmentUtils.addMaterialSwitchPreference(
@@ -352,19 +223,6 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
list.setVisible(!Preferences.getBoolValue("useOldAppVer", false));
getPreferenceScreen().addPreference(list);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"playlistsCatalogs",
"Списки плейлистов",
"Показывать вкладку плейлистов и альбомов прослушиваний в музыкальном разделе\n\nОтключение ускорит открытие музыкального раздела при медленном интернете",
null,
true,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("playlistsCatalogs", (boolean) o).apply();
return true;
}
).setVisible(!getBoolValue("useOldAppVer", false));
}
private void lastfmAuth(Context ctx) {
@@ -404,15 +262,6 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
.show();
}
private static String getAutocacheSumm() {
int autocache = Preferences.autocache();
return switch (autocache) {
case 1 -> "Только для своих";
case 2 -> "Кешировать всё";
default -> "Отключено";
};
}
public void updateLastFmPref() {
if (LastFMScrobbler.isLoggedIn()) {
findPreference("lastfm_auth").setSummary(getString(com.vtosters.lite.R.string.lastfm_authorized_as) + " " + LastFMScrobbler.getUserName());
@@ -434,43 +283,14 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
.show();
}
@SuppressLint("DefaultLocale")
private void cachedPlaylistsDialog(Context ctx) {
List<Playlist> playlists = PlaylistCacheDbDelegate.getAllPlaylists(ctx);
String[] playlistNames = new String[playlists.size()];
for (int i = 0; i < playlists.size(); i++) {
playlistNames[i] = playlists.get(i).g; // Assuming Playlist class has a getTitle() method
}
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx);
builder.setTitle("Скачанные плейлисты");
builder.setItems(playlistNames, (dialog, which) -> {
Playlist selectedPlaylist = playlists.get(which);
String playlistId = selectedPlaylist.v1();
PlaylistCacheDbDelegate.deletePlaylist(ctx, playlistId);
AndroidUtils.sendToast("Плейлист удален");
findPreference("cached_playlists").setSummary(String.format("Скачано плейлистов: %d", MusicCacheImpl.getPlaylists().size()));
});
builder.show();
}
private void delcache(Context ctx, boolean isPlaylists) {
private void delcache(Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle(com.vtosters.lite.R.string.warning)
.setMessage(com.vtosters.lite.R.string.cached_tracks_remove_confirm)
.setPositiveButton(com.vtosters.lite.R.string.yes, (dialog, which) -> {
executor.submit(() -> {
if (isPlaylists) {
PlaylistCacheDbDelegate.removeAllPlaylists(ctx);
} else {
PlaylistCacheDbDelegate.deletePlaylist(ctx, AccountManagerUtils.getUserId() + "_-1");
}
});
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(com.vtosters.lite.R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()));
})
.setNeutralButton(com.vtosters.lite.R.string.no, (dialog, which) -> dialog.cancel())
.setPositiveButton(com.vtosters.lite.R.string.yes,
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
.setNeutralButton(com.vtosters.lite.R.string.no,
(dialog, which) -> dialog.cancel())
.show();
}
@@ -60,12 +60,12 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("microgsettings").setVisible(GmsHook.isAnyServicesInstalled() && !GmsHook.isGmsInstalled());
findPreference("microgsettings").setVisible(GmsHook.isFakeGmsInstalled() && !GmsHook.isGmsInstalled());
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName(GmsHook.getCurrentGms() + ".android.gms", "org.microg.gms.ui.SettingsActivity"));
intent.setComponent(new ComponentName((GmsHook.isFakeGms2Installed() ? "app.revanced.android.gms" : "com.mgoogle.android.gms"), "org.microg.gms.ui.SettingsActivity"));
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
@@ -81,17 +81,6 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("unstableNameChangerDrop").setOnPreferenceClickListener(preference -> {
RenameTool.clearDatabase();
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("unstableNameChanger").setOnPreferenceChangeListener((preference, o) -> {
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("copydebuginfo").setOnPreferenceClickListener(preference -> {
copyText(new DeviceInfoCollector().collect().forLogging());
Toast.makeText(requireContext(), AndroidUtils.getString("device_info_copied"), Toast.LENGTH_SHORT).show();
@@ -103,11 +92,6 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("batchmessages").setOnPreferenceChangeListener((preference, o) -> {
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("copyownlink").setOnPreferenceClickListener(preference -> {
copyText("https://vk.com/id" + AccountManagerUtils.getUserId());
ToastUtils.a(requireContext().getString(R.string.link_copied));
@@ -227,23 +227,6 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
}
);
if (NetworkUtils.isNetworkConnected()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Обновить списки фильтров",
null,
null,
preference -> {
this.requireActivity().runOnUiThread(() -> {
FiltersPreferences.forceUpdateFilters();
AndroidUtils.sendToast("Обновлено");
});
return false;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -13,8 +13,6 @@ import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.VersionReader;
import static ru.vtosters.hooks.GmsHook.isSpoofPackageInstalled;
public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
@Override
@@ -48,11 +46,11 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
boolean isZenUI = OEMDetector.isZenUI();
boolean isEMUI = OEMDetector.isEMUI();
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
boolean isHyperOS = OEMDetector.isHyperOs();
boolean hasGMS = GmsHook.isGmsInstalled();
boolean hasVancedMicroG = GmsHook.isAnyServicesInstalled();
String GMSPackageName = hasGMS ? "com.google.android.gms" : hasVancedMicroG ? GmsHook.getCurrentGms() + ".android.gms" : "no gms or microg";
boolean hasVancedMicroG = GmsHook.isFakeGmsInstalled();
boolean hasReVancedMicroG = GmsHook.isFakeGms2Installed();
String GMSPackageName = hasGMS ? "com.google.android.gms" : hasVancedMicroG ? "com.mgoogle.android.gms" : hasReVancedMicroG ? "app.revanced.android.gms" : "no gms or microg";
boolean isValidSignature = Preferences.isValidSignature();
boolean isTablet = AndroidUtils.isTablet();
@@ -106,7 +104,9 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledGoogleGMS", "Value: " + hasGMS, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledMicroG", "Value: " + hasVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledVancedMicroG", "Value: " + hasVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledReVancedMicroG", "Value: " + hasReVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Product Name", productName, 0, preference -> {
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
@@ -194,8 +194,6 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isMiui", "Value: " + isMiui, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isHyperOS", "Value: " + isHyperOS, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isEMUI", "Value: " + isEMUI, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isFlyme", "Value: " + isFlyme, 0, null);
@@ -116,6 +116,17 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
var dockbarEditor = findPreference("dockbareditor");
dockbarEditor.setSummary(AndroidUtils.getString(R.string.vtldocksumm) + ": " + DockBarEditorManager.getInstance().getSelectedTabs().size());
dockbarEditor.setOnPreferenceClickListener(preference -> {
NavigatorUtils.switchFragment(requireContext(), DockBarEditorFragment.class);
return true;
});
if (Preferences.vkme() || AndroidUtils.isTablet()) {
dockbarEditor.setVisible(false);
}
if (AndroidUtils.isTablet()) {
PreferenceCategory dockbarSettingsPreferenceCategory = (PreferenceCategory) findPreference("dockbarsett");
dockbarSettingsPreferenceCategory.setVisible(false);
@@ -46,7 +46,6 @@ import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.ssfs.Utils;
import ru.vtosters.lite.themes.utils.RecolorUtils;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.ui.components.DockBarEditorManager;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
import ru.vtosters.lite.utils.*;
@@ -108,7 +107,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
boolean isLinksUnverified = AndroidUtils.isLinksUnverified(requireActivity());
boolean isDozingAvailable = Build.VERSION.SDK_INT >= 23 && !manager.isIgnoringBatteryOptimizations(AndroidUtils.getPackageName());
boolean areNotificationsDisabled = !AndroidUtils.areNotificationsEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled() && !GmsHook.isGmsInstalled();
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled();
if (AccountManagerUtils.isLogin()) {
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
@@ -534,20 +533,6 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
}
);
if (!Preferences.vkme() && !AndroidUtils.isTablet()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.dockbar_editor),
requireContext().getString(R.string.vtldocksumm) + ": " + DockBarEditorManager.getInstance().getSelectedTabs().size(),
R.drawable.ic_pin_outline_28,
preference -> {
NavigatorUtils.switchFragment(requireContext(), DockBarEditorFragment.class);
return false;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"currcache",
@@ -584,7 +569,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
}
);
if (Build.VERSION.SDK_INT >= 33 && (!OEMDetector.isMIUI() || OEMDetector.isHyperOs())) {
if (Build.VERSION.SDK_INT >= 33 && !OEMDetector.isMIUI()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -14,12 +14,10 @@ import com.vtosters.lite.api.ExtendedCommunityProfile;
import com.vtosters.lite.api.ExtendedUserProfile;
import kotlin.Unit;
import kotlin.jvm.b.Functions;
import ru.vtosters.hooks.RenameHook;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.foaf.FoafBase;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.RenameTool;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -68,12 +66,10 @@ public class MenuBuilder {
return Unit.a;
});
if (RenameHook.isEnabled()) {
addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
RenameTool.createDialog(eup, ctx);
return Unit.a;
});
}
// addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
// RenameTool.createDialog(eup, ctx);
// return Unit.a;
// });
addItem(builder, AndroidUtils.getString(isPageWhitelistedFilter ? R.string.remove_from_filter_whitelist : R.string.add_to_filter_whitelist), () -> {
setWhitelistedFilter(eup, !isPageWhitelistedFilter);
@@ -87,7 +83,7 @@ public class MenuBuilder {
return Unit.a;
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.fillInStackTrace();
e.printStackTrace();
}
}
@@ -104,12 +100,10 @@ public class MenuBuilder {
return Unit.a;
});
if (RenameHook.isEnabled()) {
addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
RenameTool.createDialogGroup(ecp, ctx);
return Unit.a;
});
}
// addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
// RenameTool.createDialogGroup(ecp, ctx);
// return Unit.a;
// });
addItem(builder, AndroidUtils.getString(isPageWhitelistedFilter ? R.string.remove_from_filter_whitelist : R.string.add_to_filter_whitelist), () -> {
setWhitelistedFilter(ecp, !isPageWhitelistedFilter);
@@ -129,7 +123,7 @@ public class MenuBuilder {
return Unit.a;
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.fillInStackTrace();
e.printStackTrace();
}
}
@@ -17,8 +17,6 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationManagerCompat;
import com.vk.core.util.AppContextHolder;
import com.vk.core.util.ToastUtils;
import com.vtosters.lite.general.fragments.WebViewFragment;
@@ -52,8 +50,14 @@ public class AndroidUtils {
@NonNull
public static Context getGlobalContext() {
return AppContextHolder.a;
} // Getting the global context to use context on application initialization
try {
Method getInitialApplicationMtd = ReflectionUtils.findMethod(Class.forName("android.app.AppGlobals"), "getInitialApplication");
return (Context) getInitialApplicationMtd.invoke(null);
} catch (Exception e) {
Log.d("GlobalContext", "Error while fetching context via refl");
}
return LifecycleUtils.getCurrentActivity();
} // Getting the global context through reflection to use context on application initialization
public static Resources getResources() {
return getGlobalContext().getResources();
@@ -1,107 +0,0 @@
package ru.vtosters.lite.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.util.Log;
import com.vk.core.network.Network;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
public class Metrics {
private static JSONArray events = new JSONArray();
public static void trackEvents(JSONObject object, boolean isMusic) {
if (NetworkUtils.isNetworkConnected()) {
handleNetworkConnected(object, isMusic);
} else {
handleNetworkDisconnected(object, isMusic);
}
}
private static void handleNetworkConnected(JSONObject object, boolean isMusic) {
if (isMusic || shouldSaveUserTraffic() || events.length() > 0) {
trackEventsImmediately(object);
} else {
trackEventList(object);
}
}
private static void handleNetworkDisconnected(JSONObject object, boolean isMusic) {
if (!isMusic) {
events.put(object);
}
}
private static boolean shouldSaveUserTraffic() {
return getBoolValue("isRoamingState", false) || isMobileNetwork();
}
private static void trackEventList(JSONObject object) {
events.put(object);
if (events.length() > 2) {
trackEventsRequest(events);
resetEvents();
}
}
private static void trackEventsImmediately(JSONObject object) {
JSONArray eventsToTrack = new JSONArray();
eventsToTrack.put(object);
trackEventsRequest(eventsToTrack);
}
private static void resetEvents() {
events = new JSONArray();
}
@SuppressWarnings("deprecation")
private static boolean isMobileNetwork() {
ConnectivityManager connectivityManager = (ConnectivityManager) AndroidUtils.getGlobalContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
android.net.Network network = connectivityManager.getActiveNetwork();
if (network != null) {
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
return networkCapabilities != null && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
}
} else {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return networkInfo != null && networkInfo.isConnected();
}
return false;
}
private static void trackEventsRequest(JSONArray object) {
Request req = new Request.a()
.b("https://" + getApi() + "/method/stats.trackEvents?events=" + object.toString() + "&v=5.119&access_token=" + getUserToken())
.a();
Network.b(CLIENT_API).a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
@Override
public void a(Call call, Response response) throws IOException {
Log.d("Metrics", response.a().g());
}
});
}
}
@@ -2,19 +2,17 @@ package ru.vtosters.lite.utils;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.vk.core.fragments.FragmentImpl;
import com.vk.extensions.NavigatorExt;
import com.vk.navigation.Navigator;
import com.vtosters.lite.TabletDialogActivity;
import ru.vtosters.lite.ui.fragments.VTSettings;
public class NavigatorUtils {
public static void switchFragment(Context context, Class<? extends FragmentImpl> fragmentClz) {
Navigator navigator = new Navigator(fragmentClz, new Bundle());
NavigatorExt.a(navigator, new TabletDialogActivity.b());
navigator.a(context);
var intent = new Navigator(fragmentClz)
.b(context)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
public static void switchFragmentNavigator(Context context, Navigator fragmentClz) {
@@ -9,24 +9,24 @@ import static ru.vtosters.lite.foaf.FoafBase.getBypassedOnlineInfo;
public class OnlineBypass {
public static JSONObject setOnlineInfo(JSONObject json) throws JSONException {
int id = json.optInt("id");
var id = json.optInt("id");
if (id == AccountManagerUtils.getUserId()) {
return json;
}
JSONObject onlineinfo = json.optJSONObject("online_info");
var onlineinfo = json.optJSONObject("online_info");
if (onlineinfo != null && !onlineinfo.optBoolean("visible") && !Preferences.serverFeaturesDisable()) {
var bypassed = getBypassedOnlineInfo(id);
if (bypassed.optInt("last_seen", 0) != 0) {
json.remove("online_info");
JSONObject online_info = new JSONObject()
var online_info = new JSONObject()
.put("visible", true)
.put("last_seen", bypassed.optInt("last_seen"))
.put("is_online", bypassed.optBoolean("is_online"))
.put("app_id", bypassed.optInt("app_id"))
.put("is_mobile", bypassed.optBoolean("is_mobile"));
JSONObject last_seen = new JSONObject()
var last_seen = new JSONObject()
.put("platform", bypassed.optInt("platform"))
.put("time", bypassed.optInt("last_seen"));
@@ -37,95 +37,51 @@ public class OnlineBypass {
return json;
}
public static JSONArray setOnlineInfoUsers(JSONArray profiles) {
if (shouldReturnProfiles(profiles)) return profiles;
public static JSONArray setOnlineInfoUsers(JSONArray profiles) throws JSONException {
if (profiles == null || profiles.length() == 0 || Preferences.serverFeaturesDisable()) return profiles;
StringBuilder sb = new StringBuilder();
int curVkId = AccountManagerUtils.getUserId();
var curVkId = AccountManagerUtils.getUserId();
for (int i = 0; i < profiles.length(); i++) {
JSONObject profile = getProfile(profiles, i);
JSONObject profile = profiles.getJSONObject(i);
int id = profile.optInt("id", -1);
JSONObject onlinfo = profile.optJSONObject("online_info");
if (shouldSkipProfile(id, curVkId, onlinfo)) continue;
sb.append(id).append(",");
if (id == curVkId || id < 0 || onlinfo == null || onlinfo.optBoolean("visible")) {
continue;
}
sb.append(id);
sb.append(",");
}
var ids = sb.toString();
if (!ids.isEmpty()) {
ids = ids.substring(0, ids.length() - 1);
} else {
return profiles;
}
String ids = prepareIds(sb);
if (ids.isEmpty()) return profiles;
JSONObject bypassedObj = getBypassedOnlineInfo(ids);
for (int i = 0; i < profiles.length(); i++) {
JSONObject profile = getProfile(profiles, i);
JSONObject profile = profiles.getJSONObject(i);
int id = profile.optInt("id");
JSONObject bypassed = bypassedObj.optJSONObject(Integer.toString(id));
if (bypassed == null) {
continue;
}
profile.remove("online_info");
if (bypassed == null) continue;
JSONObject online_info = createOnlineInfo(bypassed);
JSONObject last_seen = createLastSeen(bypassed);
updateProfile(profile, online_info, last_seen);
}
return profiles;
}
private static boolean shouldReturnProfiles(JSONArray profiles) {
return profiles == null || profiles.length() == 0 || Preferences.serverFeaturesDisable();
}
private static JSONObject getProfile(JSONArray profiles, int i) {
try {
return profiles.getJSONObject(i);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private static boolean shouldSkipProfile(int id, int curVkId, JSONObject onlinfo) {
return id == curVkId || id < 0 || onlinfo == null || onlinfo.optBoolean("visible");
}
private static String prepareIds(StringBuilder sb) {
String ids = sb.toString();
if (!ids.isEmpty()) {
ids = ids.substring(0, ids.length() - 1);
}
return ids;
}
private static JSONObject createOnlineInfo(JSONObject bypassed) {
try {
return new JSONObject()
var online_info = new JSONObject()
.put("visible", true)
.put("last_seen", bypassed.optInt("last_seen"))
.put("is_online", bypassed.optBoolean("is_online"))
.put("app_id", bypassed.optInt("app_id"))
.put("is_mobile", bypassed.optBoolean("is_mobile"));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private static JSONObject createLastSeen(JSONObject bypassed) {
try {
return new JSONObject()
var last_seen = new JSONObject()
.put("platform", bypassed.optInt("platform"))
.put("time", bypassed.optInt("last_seen"));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private static void updateProfile(JSONObject profile, JSONObject online_info, JSONObject last_seen) {
try {
profile.remove("online_info");
profile.put("last_seen", last_seen).put("online_info", online_info);
} catch (JSONException e) {
throw new RuntimeException(e);
}
return profiles;
}
}
@@ -1,6 +1,5 @@
package ru.vtosters.lite.utils;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -13,15 +12,15 @@ import android.widget.LinearLayout;
import androidx.core.util.Pair;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.dto.user.UserProfile;
import com.vk.im.engine.models.users.User;
import com.vtosters.lite.R;
import com.vtosters.lite.api.ExtendedCommunityProfile;
import com.vtosters.lite.api.ExtendedUserProfile;
import ru.vtosters.hooks.other.ThemesUtils;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.function.BiConsumer;
import static ru.vtosters.hooks.other.ThemesUtils.getSTextAttr;
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
@@ -48,40 +47,69 @@ public class RenameTool {
return helperInstance;
}
public static String getCurrentModifiedUser() {
Cursor rawQuery = getHelper().getReadableDatabase().rawQuery(String.format("SELECT * FROM %s WHERE %s=%s", TABLE_NAME, COLUMN_VKID, AccountManagerUtils.getUserId()), new String[0]);
if (rawQuery.moveToFirst()) {
try {
String decode = URLDecoder.decode(rawQuery.getString(rawQuery.getColumnIndexOrThrow(COLUMN_FIRSTNAME)), "UTF-8");
return decode + " " + URLDecoder.decode(rawQuery.getString(rawQuery.getColumnIndexOrThrow(COLUMN_LASTNAME)), "UTF-8");
} catch (java.io.UnsupportedEncodingException | java.lang.IllegalArgumentException e) {
e.printStackTrace();
} finally {
rawQuery.close();
}
} else rawQuery.close();
return null;
}
// Reload all values from DB
public static void reloadDB() {
renamedGroups.clear();
renamedUsers.clear();
updateRequested = false;
loadData(TABLE_NAME, (cursor, vkID) -> {
String firstName = decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FIRSTNAME)));
String lastName = decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LASTNAME)));
renamedUsers.put(vkID, new Pair<>(firstName, lastName));
});
loadData(TABLE_NAME_GROUP, (cursor, vkID) -> {
String name = decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)));
renamedGroups.put(vkID, name);
});
}
// Generic method to load data from the database
private static void loadData(String tableName, BiConsumer<Cursor, Integer> consumer) {
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery("SELECT * FROM " + tableName, null)) {
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(String.format("SELECT * FROM %s", TABLE_NAME), new String[0])) {
while (cursor.moveToNext()) {
String firstName = URLDecoder.decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FIRSTNAME)), "UTF-8");
String lastName = URLDecoder.decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LASTNAME)), "UTF-8");
int vkID = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_VKID));
consumer.accept(cursor, vkID);
renamedUsers.put(vkID, new Pair<>(firstName, lastName));
}
} catch (Exception e) {
throw new RuntimeException("Failed to load data from " + tableName, e);
e.printStackTrace();
}
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(String.format("SELECT * FROM %s", TABLE_NAME_GROUP), new String[0])) {
while (cursor.moveToNext()) {
String name = URLDecoder.decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)), "UTF-8");
int vkID = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_VKID));
renamedGroups.put(vkID, name);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static String decode(String value) {
public static void injectIntoChat(User u) {
int id = u.getId();
if (updateRequested) {
reloadDB();
}
Pair<String, String> user = renamedUsers.get(id);
if (user == null) return;
setObject(u, "I", user.first);
setObject(u, "J", user.second);
}
public static void setObject(User instance, String name, Object obj) {
try {
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
Field f = User.class.getDeclaredField(name);
f.setAccessible(true);
f.set(instance, obj);
} catch (Exception e) {
e.printStackTrace();
}
}
@@ -89,7 +117,7 @@ public class RenameTool {
LinearLayout linearLayout = new LinearLayout(ctx);
linearLayout.setOrientation(LinearLayout.VERTICAL);
EditText fn = new EditText(ctx);
final EditText fn = new EditText(ctx);
fn.setHint(R.string.vk_auth_sign_up_first_name);
fn.setTextColor(getTextAttr());
fn.setHintTextColor(getSTextAttr());
@@ -100,7 +128,7 @@ public class RenameTool {
margin.setMargins(dp2px(20f), 0, dp2px(20f), 0);
fn.setLayoutParams(margin);
EditText ln = new EditText(ctx);
final EditText ln = new EditText(ctx);
ln.setHint(R.string.vk_auth_sign_up_last_name);
ln.setTextColor(getTextAttr());
ln.setHintTextColor(getSTextAttr());
@@ -109,93 +137,69 @@ public class RenameTool {
ln.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ln.setLayoutParams(margin);
UserProfile profile = AccountManagerUtils.fromEup(eup);
final UserProfile profile = AccountManagerUtils.fromEup(eup);
fn.setText(AccountManagerUtils.getUserFirstName(profile));
ln.setText(AccountManagerUtils.getUserLastName(profile));
int id = AccountManagerUtils.getUserID(profile);
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx)
var builder = new VkAlertDialog.Builder(ctx)
.setTitle(R.string.rename_title)
.setMessage(R.string.rename_message)
.setView(linearLayout)
.setPositiveButton("OK", (dialog, which) -> updateName(ctx, fn.getText().toString(), ln.getText().toString(), id));
if (isChangedName(id)) {
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> deleteUser(ctx, id));
}
.setPositiveButton("OK", (dialog, which) -> {
String firstName = fn.getText().toString();
String lastName = ln.getText().toString();
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
if (isChangedName(id)) {
writableDatabase.execSQL(
String.format("UPDATE %s SET %s='%s', %s='%s' WHERE %s='%s'",
TABLE_NAME,
COLUMN_FIRSTNAME, URLEncoder.encode(firstName, "UTF-8"),
COLUMN_LASTNAME, URLEncoder.encode(lastName, "UTF-8"),
COLUMN_VKID, id));
} else {
writableDatabase.execSQL(
String.format("INSERT INTO %s (%s, %s, %s) VALUES (%s, '%s', '%s')",
TABLE_NAME,
COLUMN_VKID, COLUMN_FIRSTNAME, COLUMN_LASTNAME,
id, URLEncoder.encode(firstName, "UTF-8"), URLEncoder.encode(lastName, "UTF-8")));
}
updateRequested = true;
if (id == AccountManagerUtils.getUserId()) {
ctx.sendBroadcast(new Intent("com.vkontakte.android.USER_NAME_CHANGED"));
}
ctx.sendBroadcast(new Intent("com.vkontakte.android.ACTION_PROFILE_UPDATED").putExtra("uid", id));
sendToast(ctx.getString(R.string.rename_success));
} catch (Exception e) {
e.printStackTrace();
sendToast(ctx.getString(R.string.rename_error));
}
});
if (isChangedName(id))
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> {
getHelper().getWritableDatabase().execSQL(
String.format(
"DELETE FROM %s WHERE %s='%s'",
TABLE_NAME,
COLUMN_VKID, id));
if (id == AccountManagerUtils.getUserId()) {
ctx.sendBroadcast(new Intent("com.vkontakte.android.USER_NAME_CHANGED"));
}
updateRequested = true;
ctx.sendBroadcast(new Intent("com.vkontakte.android.ACTION_PROFILE_UPDATED").putExtra("uid", id));
sendToast(ctx.getString(R.string.rename_remove_from_bd_success));
});
builder.show();
}
public static void deleteUser(Context ctx, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
writableDatabase.delete(TABLE_NAME, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
updateRequested = true;
notifyUser(ctx, id);
sendToast(ctx.getString(R.string.rename_remove_from_bd_success));
}
public static void clearDatabase() {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
writableDatabase.delete(TABLE_NAME, null, null);
writableDatabase.delete(TABLE_NAME_GROUP, null, null);
renamedGroups.clear();
renamedUsers.clear();
updateRequested = true;
}
public static void updateName(Context ctx, String firstName, String lastName, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
ContentValues values = new ContentValues();
values.put(COLUMN_FIRSTNAME, encode(firstName));
values.put(COLUMN_LASTNAME, encode(lastName));
if (isChangedName(id)) {
// Update existing record
writableDatabase.update(TABLE_NAME, values, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
} else {
// Insert new record
values.put(COLUMN_VKID, id);
writableDatabase.insert(TABLE_NAME, null, values);
}
updateRequested = true;
notifyUser(ctx, id);
sendToast(ctx.getString(R.string.rename_success));
} catch (Exception e) {
throw new RuntimeException("Database operation failed", e);
}
}
private static String encode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
private static void notifyUser(Context ctx, int id) {
if (id == AccountManagerUtils.getUserId()) {
ctx.sendBroadcast(new Intent("com.vkontakte.android.USER_NAME_CHANGED"));
}
ctx.sendBroadcast(new Intent("com.vkontakte.android.ACTION_PROFILE_UPDATED").putExtra("uid", id));
}
public static void createDialogGroup(ExtendedCommunityProfile eup, final Context ctx) {
LinearLayout linearLayout = new LinearLayout(ctx);
linearLayout.setOrientation(LinearLayout.VERTICAL);
EditText fn = new EditText(ctx);
final EditText fn = new EditText(ctx);
fn.setHint(R.string.vk_auth_sign_up_first_name);
fn.setHintTextColor(getSTextAttr());
fn.setTextColor(getTextAttr());
@@ -206,61 +210,52 @@ public class RenameTool {
margin.setMargins(dp2px(20f), 0, dp2px(20f), 0);
fn.setLayoutParams(margin);
UserProfile profile = AccountManagerUtils.fromEup(eup);
final UserProfile profile = AccountManagerUtils.fromEup(eup);
fn.setText(AccountManagerUtils.getGroupName(profile));
int fid = AccountManagerUtils.getUserID(profile);
if (fid < 0) fid = -fid;
int id = fid;
final int id = fid;
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx)
var builder = new VkAlertDialog.Builder(ctx)
.setTitle(R.string.rename_title)
.setMessage(R.string.rename_message)
.setView(linearLayout)
.setPositiveButton("OK", (dialog, which) -> updateGroupName(ctx, fn.getText().toString(), id));
if (isChangedNameGroup(id)) {
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> deleteGroupName(ctx, id));
}
.setPositiveButton("OK", (dialog, which) -> {
String firstName = fn.getText().toString();
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
if (isChangedNameGroup(id)) {
writableDatabase.execSQL(
String.format(
"UPDATE %s SET %s='%s' WHERE %s='%s'",
TABLE_NAME_GROUP,
COLUMN_NAME, URLEncoder.encode(firstName, "UTF-8"),
COLUMN_VKID, id));
} else {
writableDatabase.execSQL(
String.format(
"INSERT INTO %s (%s, %s) VALUES (%s, '%s')",
TABLE_NAME_GROUP,
COLUMN_VKID, COLUMN_NAME,
id, URLEncoder.encode(firstName, "UTF-8")));
}
updateRequested = true;
sendToast(AndroidUtils.getString(R.string.rename_group_success));
} catch (Exception e) {
e.printStackTrace();
}
});
if (isChangedNameGroup(id))
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> {
getHelper().getWritableDatabase().execSQL(String.format("DELETE FROM %s WHERE %s='%s'", TABLE_NAME_GROUP, COLUMN_VKID, id));
updateRequested = true;
sendToast(ctx.getString(R.string.rename_remove_group_from_bd_success));
});
builder.show();
}
public static void deleteGroupName(Context ctx, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
writableDatabase.delete(TABLE_NAME_GROUP, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
updateRequested = true;
sendToast(ctx.getString(R.string.rename_remove_group_from_bd_success));
}
public static void updateGroupName(Context ctx, String groupName, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
ContentValues values = new ContentValues();
values.put(COLUMN_NAME, encode(groupName));
if (isChangedNameGroup(id)) {
// Update existing record
writableDatabase.update(TABLE_NAME_GROUP, values, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
} else {
// Insert new record
values.put(COLUMN_VKID, id);
writableDatabase.insert(TABLE_NAME_GROUP, null, values);
}
updateRequested = true;
sendToast(AndroidUtils.getString(R.string.rename_group_success));
} catch (Exception e) {
throw new RuntimeException("Database operation failed", e);
}
}
public static boolean isChangedName(int uid) {
return renamedUsers.get(uid) != null;
}
@@ -269,6 +264,10 @@ public class RenameTool {
return renamedGroups.get(uid) != null;
}
public static boolean isIdInList(UserProfile profile) {
return true;
}
private static class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
@@ -96,15 +96,14 @@ public class VTVerifications {
}
private static void processIds(JSONArray jsonIds, List<Integer> member) {
if (jsonIds == null || jsonIds.length() == 0) {
if (jsonIds == null || jsonIds.length() == 0)
return;
}
for (int i = 0; i < jsonIds.length(); i++) {
int id = jsonIds.optInt(i);
if (id != 0) {
member.add(id);
try {
member.add(jsonIds.getInt(i));
} catch (JSONException e) {
e.fillInStackTrace();
}
}
}
@@ -9,6 +9,7 @@ import static ru.vtosters.hooks.other.ThemesUtils.*;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class WebViewColoringUtils {
public static boolean isLoaded = false;
public static String loadedCSS = "";
public static String loadedCSSAmoled = "";
@@ -52,6 +53,7 @@ public class WebViewColoringUtils {
public static void load() {
loadedCSS = readCSS("vt_ui_accent.css");
loadedCSSAmoled = readCSS("vt_ui_oled.css");
isLoaded = true;
}
public static void inject(WebView webView, String str) {
@@ -11,40 +11,32 @@ public class MusicCacheStorageUtils {
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File getTrackDirById(String trackId) {
File res = new File(getCacheStorageDir(), trackId);
public static File getTrackDirById(final String trackId) {
final var res = new File(getCacheStorageDir(), trackId);
res.mkdirs();
return res;
}
public static File getTrackFile(String trackId) {
public static File getTrackFile(final String trackId) {
return new File(getTrackDirById(trackId), "track.mp3");
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File getThumbDirById(String trackId) {
File res = new File(getTrackDirById(trackId), "thumbs");
public static File getThumbDirById(final String trackId) {
final var res = new File(getTrackDirById(trackId), "thumbs");
res.mkdirs();
return res;
}
public static File getTrackThumb(String trackId, int factor) {
public static File getTrackThumb(final String trackId, final int factor) {
return new File(getThumbDirById(trackId), "thumb_" + factor + ".png");
}
public static File getPlaylistThumb(String playlistId, int factor) {
return new File(getThumbDirById(playlistId), "thumb_playlist_" + factor + ".png");
}
public static void removePlaylistThumb(String playlistId) {
IOUtils.deleteRecursive(getThumbDirById(playlistId));
}
public static void removeTrackDirById(String trackId) {
public static void removeTrackDirById(final String trackId) {
IOUtils.deleteRecursive(getTrackDirById(trackId));
}
public static void clear() {
IOUtils.deleteRecursive(getCacheStorageDir());
}
}
}
@@ -1,18 +0,0 @@
package ru.vtosters.lite.utils.music;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Artist;
import java.util.stream.Collectors;
public class MusicTrackUtils {
public static String getArtists(MusicTrack musicTrack) {
return (musicTrack.L == null || musicTrack.I == null
? normalizeMetadata(musicTrack.C)
: normalizeMetadata(musicTrack.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
}
private static String normalizeMetadata(String in) {
return in.replaceAll("[\\\\/:*?\"<>|]", "");
}
}
@@ -1,17 +0,0 @@
package ru.vtosters.lite.utils.music;
import android.util.Log;
import com.vk.dto.music.Playlist;
import org.json.JSONObject;
public class PlaylistUtils {
public static JSONObject getThumb(Playlist playlist) {
try {
Log.d("Playlist", "thumb is not null");
return playlist.F.J();
} catch (Exception e) {
Log.d("Playlist", "thumb is null");
return null;
}
}
}
@@ -3,7 +3,6 @@ package ru.vtosters.sponsorpost.internal;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import java.util.HashSet;
@@ -23,7 +22,7 @@ public class VotesPreferences {
}
public static boolean isEnabled() {
return preferences.getBoolean("sponsortesting", false) && !Preferences.serverFeaturesDisable();
return preferences.getBoolean("sponsortesting", false);
}
public static void setEnabled(boolean status) {
@@ -61,18 +61,6 @@ public class FiltersPreferences {
editor.apply();
}
public static void forceUpdateFilters() {
SharedPreferences.Editor editor = preferences.edit();
preferences.getAll().keySet().stream()
.filter(key -> key.endsWith(":" + PREF_KEY_VERSION))
.forEach(key -> editor.putString(key, "0"));
editor.apply();
Updates.updateFilters();
}
public static boolean getSavedKeyValue(int id) {
return getFilter(id) != null;
}
@@ -3,7 +3,6 @@ package ru.vtosters.sponsorpost.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.data.Post;
@@ -24,7 +23,7 @@ public class PostsPreferences {
}
public static boolean isEnabled() {
return preferences.getBoolean("sponsorpost", true) && !Preferences.serverFeaturesDisable();
return preferences.getBoolean("sponsorpost", true);
}
public static void setEnabled(boolean status) {
@@ -1,6 +1,5 @@
package ru.vtosters.sponsorpost.utils;
import android.util.Log;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.lite.utils.newsfeed.NewsFeedFiltersUtils;
import ru.vtosters.sponsorpost.data.Filter;
@@ -32,17 +31,14 @@ public class Updates {
for (Filter filter : filters) {
Filter localFilter = findLocalFilterById(localFilters, filter.getId());
if (localFilter == null) {
Log.d("Updates", "local filter is not exist, adding new: " + filter.getId());
FiltersPreferences.saveFilter(filter);
} else {
String remoteVersion = filter.getVersion();
String localVersion = localFilter.getVersion();
if (Objects.equals(remoteVersion, localVersion)) {
Log.d("Updates", "saved local filter: " + localFilter.getId());
FiltersPreferences.saveFilter(localFilter);
} else {
Log.d("Updates", "saved remote filter: " + filter.getId());
FiltersPreferences.saveFilter(filter);
} else {
FiltersPreferences.saveFilter(localFilter);
}
}
}
-1
View File
@@ -140,7 +140,6 @@
<uses-permission android:name="com.mgoogle.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="app.revanced.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
<application
-2
View File
@@ -75,7 +75,6 @@
--header_tint: %header_text% !important;
--header_tint_alternate: %header_text% !important;
--vkui--color_icon_accent_themed: %header_text% !important;
--vkui--color_text_accent_themed: %header_text% !important;
}
@@ -140,7 +139,6 @@
--header_tint: %header_text% !important;
--header_tint_alternate: %header_text% !important;
--vkui--color_icon_accent_themed: %header_text% !important;
--vkui--color_text_accent_themed: %header_text% !important;
--color_field_border: %field_border% !important;
--vkui--color_field_border: %field_border% !important;
--color_field_background: %field_background% !important;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More