Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44fa23020e | ||
|
|
4cdcf9aee0 | ||
|
|
63c5262444 | ||
|
|
9074e80478 | ||
|
|
f7f43928cf | ||
|
|
1accb45f12 | ||
|
|
d9f53920e5 | ||
|
|
76d91332d7 | ||
|
|
728cbde683 | ||
|
|
60c98cd998 | ||
|
|
f1d106e0f0 | ||
|
|
c92194e90b | ||
|
|
91683af807 | ||
|
|
c5c898aa4e | ||
|
|
25634b2469 | ||
|
|
b8e764d09d | ||
|
|
17ed32857a | ||
|
|
d631d07e0f | ||
|
|
2ec4c69832 | ||
|
|
6426c965c6 | ||
|
|
4f6d410185 | ||
|
|
18a899f439 | ||
|
|
7e5b3fe233 | ||
|
|
4abd0eec6c | ||
|
|
13fab86b83 | ||
|
|
5ada5d74d2 | ||
|
|
aa1802c371 | ||
|
|
0cf1c6ecba | ||
|
|
f051420ff9 | ||
|
|
3902a40e30 | ||
|
|
0c97f15088 | ||
|
|
4cf112921b | ||
|
|
6e31b26af0 | ||
|
|
6f22b5dd03 | ||
|
|
9ee1bd0f79 | ||
|
|
56b11bc43c | ||
|
|
a476d490a6 | ||
|
|
74b50fb9c2 | ||
|
|
ddaedd0ba0 | ||
|
|
729342085a | ||
|
|
bb20178dc9 | ||
|
|
306cee1fd4 | ||
|
|
02e8732e9f |
@@ -84,6 +84,12 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task testClasses {
|
||||||
|
doLast {
|
||||||
|
println 'This is a dummy testClasses task'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.1'
|
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.1'
|
||||||
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.1'
|
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.1'
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+2
-1
@@ -43,4 +43,5 @@
|
|||||||
|
|
||||||
-keep class com.vtosters.lite.** { *; }
|
-keep class com.vtosters.lite.** { *; }
|
||||||
-keep class com.vk.** { *; }
|
-keep class com.vk.** { *; }
|
||||||
-keep class bruhcollective.itaysonlab.libvkx.client.** { *; }
|
-keep class bruhcollective.itaysonlab.libvkx.client.** { *; }
|
||||||
|
-keep class com.guardanis.applock.** { *; }
|
||||||
@@ -10,9 +10,9 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||||
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
|
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
|
||||||
|
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||||
|
|
||||||
public class LibVKXClient {
|
public class LibVKXClient {
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@@ -80,11 +80,6 @@ public class LibVKXClient {
|
|||||||
lambdaplay(list, musicTrack, iLibVkxService, null);
|
lambdaplay(list, musicTrack, iLibVkxService, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String asId(MusicTrack musicTrack) {
|
public static String asId(MusicTrack musicTrack)
|
||||||
String id = musicTrack.e + "_" + musicTrack.d;
|
{ return musicTrack.y1(); }
|
||||||
if (musicTrack.O != null && !musicTrack.O.equals("")) {
|
|
||||||
id += "_" + musicTrack.O;
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
package com.guardanis.applock;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
|
import androidx.core.os.CancellationSignal;
|
||||||
|
|
||||||
|
import com.vtosters.lite.R;
|
||||||
|
|
||||||
|
import com.guardanis.applock.activities.UnlockActivity;
|
||||||
|
import com.guardanis.applock.services.FingerprintLockService;
|
||||||
|
import com.guardanis.applock.services.FingerprintLockService.AuthenticationDelegate;
|
||||||
|
import com.guardanis.applock.services.LockService;
|
||||||
|
import com.guardanis.applock.services.PINLockService;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class AppLock {
|
||||||
|
|
||||||
|
public static final int REQUEST_CODE_UNLOCK = 9371;
|
||||||
|
public static final int REQUEST_CODE_LOCK_CREATION = 9372;
|
||||||
|
public static final int REQUEST_CODE_FINGERPRINT_PERMISSION = 9373;
|
||||||
|
public static final int ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE = 1;
|
||||||
|
public static final int ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED = 2;
|
||||||
|
public static final int ERROR_CODE_FINGERPRINTS_EMPTY = 3;
|
||||||
|
public static final int ERROR_CODE_FINGERPRINTS_NOT_LOCALLY_ENROLLED = 4;
|
||||||
|
public static final int ERROR_CODE_SDK_VERSION_MINIMUM = 5;
|
||||||
|
private static final String PREFS = "pin__preferences";
|
||||||
|
private static final String PREF_UNLOCK_FAILURE_TIME = "pin__unlock_failure_time";
|
||||||
|
private static final String PREF_UNLOCK_SUCCESS_TIME = "pin__unlock_success_time";
|
||||||
|
private static AppLock instance;
|
||||||
|
protected Context context;
|
||||||
|
protected HashMap<Class<? extends LockService>, LockService> lockServices = new HashMap<>();
|
||||||
|
protected int unlockAttemptsCount = 1;
|
||||||
|
|
||||||
|
protected AppLock(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
|
||||||
|
this.lockServices.put(PINLockService.class, new PINLockService());
|
||||||
|
this.lockServices.put(FingerprintLockService.class, new FingerprintLockService());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppLock getInstance(Context context) {
|
||||||
|
if (instance == null)
|
||||||
|
instance = new AppLock(context);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the user has enrolled in either PIN or Fingerprint locking
|
||||||
|
*/
|
||||||
|
public static boolean isEnrolled(Context context) {
|
||||||
|
AppLock helper = getInstance(context);
|
||||||
|
|
||||||
|
for (LockService service : helper.lockServices.values()) {
|
||||||
|
if (service.isEnrolled(context))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setAuthenticationRequired(Context context) {
|
||||||
|
AppLock helper = getInstance(context);
|
||||||
|
|
||||||
|
helper.getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putLong(PREF_UNLOCK_SUCCESS_TIME, 0)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the user is enrolled in locking and the last successful unlock happened more than the default lock duration ago
|
||||||
|
*/
|
||||||
|
public static boolean isUnlockRequired(Context context) {
|
||||||
|
int minutes = Integer.parseInt(getPinCodeTime(context));
|
||||||
|
|
||||||
|
if (minutes < 1) {
|
||||||
|
minutes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isUnlockRequired(context, TimeUnit.MINUTES.toMillis(minutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPinCodeTime(Context context) {
|
||||||
|
return context.getString(AndroidUtils.getIdentifier("applock__activity_lock_reenable_minutes", "integer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPinCodeLimits(Context context) {
|
||||||
|
return context.getString(AndroidUtils.getIdentifier("applock__input_pin_item_count", "integer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the user is enrolled in locking and the last successful unlock happened more than lastSuccessValidMs ago
|
||||||
|
*/
|
||||||
|
public static boolean isUnlockRequired(Context context, long lastSuccessValidMs) {
|
||||||
|
return isEnrolled(context) && lastSuccessValidMs < System.currentTimeMillis() - getUnlockSuccessTime(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static long getUnlockSuccessTime(Context context) {
|
||||||
|
return AppLock.getInstance(context)
|
||||||
|
.getPreferences()
|
||||||
|
.getLong(PREF_UNLOCK_SUCCESS_TIME, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onActivityResumed(Activity activity) {
|
||||||
|
if (isUnlockRequired(activity)) {
|
||||||
|
boolean unlockActivityReturnAllowed = activity.getResources()
|
||||||
|
.getBoolean(AndroidUtils.getIdentifier("applock__unlock_activity_return_allowed", "bool"));
|
||||||
|
|
||||||
|
Intent intent = new Intent(activity, UnlockActivity.class)
|
||||||
|
.putExtra(UnlockActivity.INTENT_ALLOW_UNLOCKED_EXIT, unlockActivityReturnAllowed);
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, REQUEST_CODE_UNLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an action-based unlock is required and navigates to the UnlockActivity if true.
|
||||||
|
*
|
||||||
|
* @return true if unlock is required.
|
||||||
|
*/
|
||||||
|
public static boolean unlockIfRequired(Activity activity) {
|
||||||
|
if (isUnlockRequired(activity)) {
|
||||||
|
Intent intent = new Intent(activity, UnlockActivity.class)
|
||||||
|
.putExtra(UnlockActivity.INTENT_ALLOW_UNLOCKED_EXIT, true);
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, REQUEST_CODE_UNLOCK);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attemptFingerprintUnlock(final UnlockDelegate eventListener) {
|
||||||
|
if (handleFailureBlocking(eventListener))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AuthenticationDelegate delegate = new AuthenticationDelegate() {
|
||||||
|
@Override
|
||||||
|
public void onResolutionRequired(int errorCode) {
|
||||||
|
// eventListener.onResolutionRequired(errorCode);
|
||||||
|
eventListener.requestOpenKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationHelp(int code, CharSequence message) {
|
||||||
|
eventListener.onAuthenticationHelp(code, String.valueOf(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticating(CancellationSignal cancellationSignal) {
|
||||||
|
// Handled internally
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(@NonNull FingerprintManagerCompat.AuthenticationResult result) {
|
||||||
|
onUnlockSuccessful(eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailed(String message) {
|
||||||
|
Log.w(getClass().getSimpleName(), "Fingerprint authentication failed: " + message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getLockService(FingerprintLockService.class)
|
||||||
|
.authenticate(context, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attemptPINUnlock(String pin, final UnlockDelegate eventListener) {
|
||||||
|
if (handleFailureBlocking(eventListener))
|
||||||
|
return;
|
||||||
|
|
||||||
|
PINLockService.AuthenticationDelegate delegate = new PINLockService.AuthenticationDelegate() {
|
||||||
|
@Override
|
||||||
|
public void onNoPIN() {
|
||||||
|
onUnlockFailed(context.getString(AndroidUtils.getIdentifier("applock__unlock_error_no_matching_pin_found", "string")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPINDoesNotMatch() {
|
||||||
|
onUnlockFailed(context.getString(AndroidUtils.getIdentifier("applock__unlock_error_match_failed", "string")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPINMatches() {
|
||||||
|
onUnlockSuccessful(eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUnlockFailed(String message) {
|
||||||
|
handleUnlockFailure(message, eventListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getLockService(PINLockService.class)
|
||||||
|
.authenticate(context, pin, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if failure blocking is enabled
|
||||||
|
*/
|
||||||
|
private boolean handleFailureBlocking(final UnlockDelegate eventListener) {
|
||||||
|
if (isUnlockFailureBlockEnabled()) {
|
||||||
|
unlockAttemptsCount++;
|
||||||
|
|
||||||
|
if (getFailureDelayMs() < System.currentTimeMillis() - getUnlockFailureBlockStart())
|
||||||
|
resetUnlockFailure();
|
||||||
|
else {
|
||||||
|
String message = String.format(
|
||||||
|
context.getString(AndroidUtils.getIdentifier("applock__unlock_error_retry_limit_exceeded", "string")),
|
||||||
|
formatTimeRemaining());
|
||||||
|
|
||||||
|
if (eventListener != null)
|
||||||
|
eventListener.onFailureLimitExceeded(message);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleUnlockFailure(String message, UnlockDelegate eventListener) {
|
||||||
|
this.unlockAttemptsCount++;
|
||||||
|
|
||||||
|
if (eventListener != null)
|
||||||
|
eventListener.onFailureLimitExceeded(message);
|
||||||
|
|
||||||
|
if (context.getResources().getInteger(AndroidUtils.getIdentifier("applock__max_retry_count", "integer")) < unlockAttemptsCount)
|
||||||
|
onFailureExceedsLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedPreferences getPreferences() {
|
||||||
|
return context.getSharedPreferences(PREFS, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onFailureExceedsLimit() {
|
||||||
|
getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putLong(PREF_UNLOCK_FAILURE_TIME, System.currentTimeMillis())
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUnlockFailureBlockEnabled() {
|
||||||
|
return context.getResources().getInteger(AndroidUtils.getIdentifier("applock__max_retry_count", "integer")) < unlockAttemptsCount
|
||||||
|
|| System.currentTimeMillis() - getUnlockFailureBlockStart() < getFailureDelayMs();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getUnlockFailureBlockStart() {
|
||||||
|
return getPreferences()
|
||||||
|
.getLong(PREF_UNLOCK_FAILURE_TIME, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onUnlockSuccessful(UnlockDelegate eventListener) {
|
||||||
|
getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putLong(PREF_UNLOCK_SUCCESS_TIME, System.currentTimeMillis())
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
resetUnlockFailure();
|
||||||
|
|
||||||
|
if (eventListener != null)
|
||||||
|
eventListener.onUnlockSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the last login time to require the user to re-authenticate with
|
||||||
|
* AppLock the next time they are eligible to unlock.
|
||||||
|
*/
|
||||||
|
public void setAuthenticationRequired() {
|
||||||
|
getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putLong(PREF_UNLOCK_SUCCESS_TIME, 0)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resetUnlockFailure() {
|
||||||
|
unlockAttemptsCount = 1;
|
||||||
|
|
||||||
|
getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putLong(PREF_UNLOCK_FAILURE_TIME, 0)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String formatTimeRemaining() {
|
||||||
|
long millis = getFailureDelayMs() - (System.currentTimeMillis() - getUnlockFailureBlockStart());
|
||||||
|
long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis));
|
||||||
|
|
||||||
|
if (TimeUnit.MILLISECONDS.toMinutes(millis) < 1)
|
||||||
|
return String.format(AndroidUtils.getString(R.string.n_seconds), seconds);
|
||||||
|
else
|
||||||
|
return String.format(AndroidUtils.getString(R.string.n_mins_secs), TimeUnit.MILLISECONDS.toMinutes(millis), seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getFailureDelayMs() {
|
||||||
|
return TimeUnit.MINUTES.toMillis(context.getResources()
|
||||||
|
.getInteger(AndroidUtils.getIdentifier("applock__failure_retry_delay", "integer")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will remove all PIN and/or Fingerprint enrollment data.
|
||||||
|
* Users will need to re-enroll in AppLock after this call.
|
||||||
|
*/
|
||||||
|
public void invalidateEnrollments() {
|
||||||
|
resetUnlockFailure();
|
||||||
|
setAuthenticationRequired();
|
||||||
|
|
||||||
|
for (LockService service : lockServices.values())
|
||||||
|
service.invalidateEnrollments(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelPendingAuthentications() {
|
||||||
|
for (LockService service : lockServices.values())
|
||||||
|
service.cancelPendingAuthentications(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends LockService> T getLockService(Class<T> named) {
|
||||||
|
return (T) lockServices.get(named);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UnlockDelegate {
|
||||||
|
void onUnlockSuccessful();
|
||||||
|
|
||||||
|
void onResolutionRequired(int errorCode);
|
||||||
|
|
||||||
|
void onAuthenticationHelp(int code, String message);
|
||||||
|
|
||||||
|
void onFailureLimitExceeded(String message);
|
||||||
|
void requestOpenKeyboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.guardanis.applock.activities;
|
||||||
|
|
||||||
|
import static ru.vtosters.hooks.other.ThemesUtils.getBackgroundContent;
|
||||||
|
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
|
||||||
|
import static ru.vtosters.hooks.other.ThemesUtils.isDarkTheme;
|
||||||
|
import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowInsetsController;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.guardanis.applock.views.LockCreationViewController;
|
||||||
|
|
||||||
|
|
||||||
|
public class LockCreationActivity extends AppCompatActivity implements LockCreationViewController.Delegate {
|
||||||
|
|
||||||
|
protected LockCreationViewController viewController;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstance) {
|
||||||
|
super.onCreate(savedInstance);
|
||||||
|
|
||||||
|
setContentView(getIdentifier("applock__activity_lock_creation", "layout"));
|
||||||
|
findViewById(getIdentifier("applock__activity_lock_creation", "id")).setBackgroundColor(getBackgroundContent());
|
||||||
|
((TextView) findViewById(getIdentifier("pin__description", "id"))).setTextColor(getTextAttr());
|
||||||
|
|
||||||
|
this.viewController = new LockCreationViewController(this, findViewById(getIdentifier("pin__container", "id")));
|
||||||
|
this.viewController.setDelegate(this);
|
||||||
|
this.viewController.setupRootFlow();
|
||||||
|
|
||||||
|
this.getWindow().setStatusBarColor(getBackgroundContent());
|
||||||
|
this.getWindow().setNavigationBarColor(getBackgroundContent());
|
||||||
|
|
||||||
|
if (!isDarkTheme()) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
this.getWindow().getInsetsController().setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
|
||||||
|
} else {
|
||||||
|
this.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLockCreated() {
|
||||||
|
setResult(Activity.RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointerCaptureChanged(boolean hasCapture) {
|
||||||
|
super.onPointerCaptureChanged(hasCapture);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.guardanis.applock.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
|
||||||
|
public class LockableAppCompatActivity extends AppCompatActivity {
|
||||||
|
@Override
|
||||||
|
protected void onPostResume() {
|
||||||
|
super.onPostResume();
|
||||||
|
|
||||||
|
AppLock.onActivityResumed(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
if (requestCode == AppLock.REQUEST_CODE_UNLOCK && resultCode == Activity.RESULT_CANCELED)
|
||||||
|
handleUnlockActivityReturnFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleUnlockActivityReturnFailure() {
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.guardanis.applock.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowInsetsController;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.guardanis.applock.views.UnlockViewController;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
import static ru.vtosters.hooks.other.ThemesUtils.getBackgroundContent;
|
||||||
|
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
|
||||||
|
import static ru.vtosters.hooks.other.ThemesUtils.isDarkTheme;
|
||||||
|
|
||||||
|
public class UnlockActivity extends AppCompatActivity implements UnlockViewController.Delegate {
|
||||||
|
|
||||||
|
public static final String INTENT_ALLOW_UNLOCKED_EXIT = "pin__allow_activity_exit"; // false by default
|
||||||
|
|
||||||
|
protected UnlockViewController viewController;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstance) {
|
||||||
|
super.onCreate(savedInstance);
|
||||||
|
|
||||||
|
setContentView(AndroidUtils.getIdentifier("applock__activity_unlock", "layout"));
|
||||||
|
findViewById(AndroidUtils.getIdentifier("applock__activity_unlock", "id")).setBackgroundColor(getBackgroundContent());
|
||||||
|
((TextView) findViewById(AndroidUtils.getIdentifier("pin__description", "id"))).setTextColor(getTextAttr());
|
||||||
|
|
||||||
|
this.viewController = new UnlockViewController(this, findViewById(AndroidUtils.getIdentifier("pin__container", "id")));
|
||||||
|
this.viewController.setAutoAuthorizationEnabled(false); // Disable auto authorization so fingerprint doesn't crash onResume
|
||||||
|
this.viewController.setDelegate(this);
|
||||||
|
this.viewController.setupRootFlow();
|
||||||
|
this.viewController.setAutoAuthorizationEnabled(true);
|
||||||
|
|
||||||
|
this.getWindow().setStatusBarColor(getBackgroundContent());
|
||||||
|
this.getWindow().setNavigationBarColor(getBackgroundContent());
|
||||||
|
|
||||||
|
if (!isDarkTheme()) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
this.getWindow().getInsetsController().setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
|
||||||
|
} else {
|
||||||
|
this.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnlockSuccessful() {
|
||||||
|
setResult(Activity.RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
if (event.getRepeatCount() == 0) handleBackPressed();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
handleBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleBackPressed() {
|
||||||
|
if (!getIntent().getBooleanExtra(INTENT_ALLOW_UNLOCKED_EXIT, false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointerCaptureChanged(boolean hasCapture) {
|
||||||
|
super.onPointerCaptureChanged(hasCapture);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.guardanis.applock.dialogs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatDialog;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.views.AppLockViewController;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public abstract class AppLockDialogBuilder<ALVC extends AppLockViewController> {
|
||||||
|
|
||||||
|
protected WeakReference<Activity> activity;
|
||||||
|
protected ALVC viewController;
|
||||||
|
|
||||||
|
protected WeakReference<AppCompatDialog> dialog = new WeakReference<AppCompatDialog>(null);
|
||||||
|
protected int layoutResId;
|
||||||
|
|
||||||
|
public AppLockDialogBuilder(Activity activity, int layoutResId) {
|
||||||
|
this.activity = new WeakReference<>(activity);
|
||||||
|
this.layoutResId = layoutResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialog show() {
|
||||||
|
if (viewController != null)
|
||||||
|
throw new RuntimeException("You can only call show() once per AppLockDialogBuilder instance.");
|
||||||
|
|
||||||
|
final Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
View parent = activity.getLayoutInflater()
|
||||||
|
.inflate(layoutResId, null, false);
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
builder.setView(parent);
|
||||||
|
builder.setOnCancelListener(dialogInterface -> handleCanceled());
|
||||||
|
builder.setOnDismissListener(dialogInterface -> handleDismissed());
|
||||||
|
|
||||||
|
this.viewController = buildViewControllerInstance(parent);
|
||||||
|
this.viewController.setupRootFlow();
|
||||||
|
|
||||||
|
AppCompatDialog dialog = builder.show();
|
||||||
|
|
||||||
|
this.dialog = new WeakReference<>(dialog);
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ALVC buildViewControllerInstance(View parent);
|
||||||
|
|
||||||
|
protected void handleCanceled() {
|
||||||
|
dismissDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleDismissed() {
|
||||||
|
this.dialog = new WeakReference<>(null);
|
||||||
|
this.viewController.unregisterReceivers();
|
||||||
|
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.cancelPendingAuthentications();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dismissDialog() {
|
||||||
|
try {
|
||||||
|
AppCompatDialog dialog = this.dialog.get();
|
||||||
|
|
||||||
|
if (dialog == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Attempt to prevent stupid fucking uncatchable system error "View not Attached to Window" when dialog dismissed
|
||||||
|
Context context = ((ContextWrapper) dialog.getContext()).getBaseContext();
|
||||||
|
if (context instanceof Activity) {
|
||||||
|
if (!((Activity) context).isFinishing()) {
|
||||||
|
if (!((Activity) context).isDestroyed())
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
dialog.dismiss();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e("AppLockDialogBuilder", "View not Attached to Window???", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dialog = new WeakReference<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ALVC getViewController() {
|
||||||
|
return viewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.guardanis.applock.dialogs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.guardanis.applock.views.LockCreationViewController;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class LockCreationDialogBuilder extends AppLockDialogBuilder<LockCreationViewController> implements LockCreationViewController.Delegate {
|
||||||
|
|
||||||
|
protected Runnable lockCreatedCallback;
|
||||||
|
protected Runnable canceledCallback;
|
||||||
|
|
||||||
|
public LockCreationDialogBuilder(Activity activity) {
|
||||||
|
super(activity, AndroidUtils.getIdentifier("applock__lock_creation", "layout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Runnable to be triggered when a user has successfully enrolled in locking.
|
||||||
|
*/
|
||||||
|
public LockCreationDialogBuilder onLockCreated(Runnable lockCreatedCallback) {
|
||||||
|
this.lockCreatedCallback = lockCreatedCallback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Runnable to be triggered when a user has canceled enrollment.
|
||||||
|
*/
|
||||||
|
public LockCreationDialogBuilder onCanceled(Runnable canceledCallback) {
|
||||||
|
this.canceledCallback = canceledCallback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LockCreationViewController buildViewControllerInstance(View parent) {
|
||||||
|
LockCreationViewController controller = new LockCreationViewController(activity.get(), parent);
|
||||||
|
controller.setDelegate(this);
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLockCreated() {
|
||||||
|
dismissDialog();
|
||||||
|
|
||||||
|
if (lockCreatedCallback != null)
|
||||||
|
lockCreatedCallback.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleCanceled() {
|
||||||
|
super.handleCanceled();
|
||||||
|
|
||||||
|
if (canceledCallback != null)
|
||||||
|
canceledCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.guardanis.applock.dialogs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.views.UnlockViewController;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class UnlockDialogBuilder extends AppLockDialogBuilder<UnlockViewController> implements UnlockViewController.Delegate {
|
||||||
|
|
||||||
|
protected Runnable unlockCallback;
|
||||||
|
protected Runnable canceledCallback;
|
||||||
|
|
||||||
|
public UnlockDialogBuilder(Activity activity) {
|
||||||
|
super(activity, AndroidUtils.getIdentifier("applock__unlock", "layout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Runnable to be triggered when a user has successfully unlocked.
|
||||||
|
*/
|
||||||
|
public UnlockDialogBuilder onUnlocked(Runnable unlockCallback) {
|
||||||
|
this.unlockCallback = unlockCallback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Runnable to be triggered when a user has canceled unlocking.
|
||||||
|
*/
|
||||||
|
public UnlockDialogBuilder onCanceled(Runnable canceledCallback) {
|
||||||
|
this.canceledCallback = canceledCallback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UnlockViewController buildViewControllerInstance(View parent) {
|
||||||
|
UnlockViewController controller = new UnlockViewController(activity.get(), parent);
|
||||||
|
controller.setDelegate(this);
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnlockSuccessful() {
|
||||||
|
dismissDialog();
|
||||||
|
triggerUnlockCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleCanceled() {
|
||||||
|
super.handleCanceled();
|
||||||
|
|
||||||
|
if (canceledCallback != null)
|
||||||
|
canceledCallback.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void triggerUnlockCallback() {
|
||||||
|
if (unlockCallback != null)
|
||||||
|
unlockCallback.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is actually enrolled in locking before opening the unlock dialog.
|
||||||
|
* If the user is enrolled, show() will be called. Otherwise, this function will trigger this
|
||||||
|
* instance's UnlockDialogBuilder.unlockCallback immediately.
|
||||||
|
*
|
||||||
|
* @return Dialog if show() is called
|
||||||
|
*/
|
||||||
|
public Dialog showIfEnrolledOrSuccess() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!AppLock.isEnrolled(activity)) {
|
||||||
|
triggerUnlockCallback();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is both enrolled in locking and that unlocking is required before
|
||||||
|
* opening the unlock dialog. If the user is enrolled, show() will be called. Otherwise,
|
||||||
|
* this function will trigger this instance's UnlockDialogBuilder.unlockCallback immediately.
|
||||||
|
*
|
||||||
|
* @param longValidDurationMs the milliseconds a user's last unlock is valid for
|
||||||
|
* @return Dialog if show() is called
|
||||||
|
*/
|
||||||
|
public Dialog showIfRequiredOrSuccess(long longValidDurationMs) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!AppLock.isUnlockRequired(activity, longValidDurationMs)) {
|
||||||
|
triggerUnlockCallback();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.guardanis.applock.pin;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
interface InputTextListener {
|
||||||
|
void onTextEntered(int count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PINInputController implements TextView.OnEditorActionListener, InputTextListener {
|
||||||
|
|
||||||
|
private final WeakReference<PINInputView> inputView;
|
||||||
|
private WeakReference<InputEventListener> eventListener = new WeakReference<>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you want to actually receive events after this, make sure you call setInputEventListener
|
||||||
|
*/
|
||||||
|
public PINInputController(PINInputView inputView) {
|
||||||
|
this.inputView = new WeakReference<>(inputView);
|
||||||
|
|
||||||
|
inputView.setOnEditorActionListener(this);
|
||||||
|
inputView.setInputTextListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputController ensureKeyboardVisible() {
|
||||||
|
final PINInputView inputView = this.inputView.get();
|
||||||
|
|
||||||
|
if (inputView == null)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
inputView.postDelayed(inputView::ensureKeyboardVisible, 300);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputController setPasswordCharactersEnabled(boolean passwordCharacterEnabled) {
|
||||||
|
this.inputView.get().setPasswordCharactersEnabled(passwordCharacterEnabled);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputController setInputNumbersCount(int inputNumbersCount) {
|
||||||
|
this.inputView.get().setInputViewsCount(inputNumbersCount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputController setInputEventListener(InputEventListener eventListener) {
|
||||||
|
this.eventListener = new WeakReference<>(eventListener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
|
||||||
|
PINInputView inputView = this.inputView.get();
|
||||||
|
|
||||||
|
if (inputView == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (isSoftKeyboardFinishedAction(textView, i, keyEvent)) {
|
||||||
|
InputEventListener eventListener = this.eventListener.get();
|
||||||
|
|
||||||
|
if (eventListener != null)
|
||||||
|
eventListener.onInputEntered(inputView.getText());
|
||||||
|
|
||||||
|
inputView.reset();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSoftKeyboardFinishedAction(TextView view, int action, KeyEvent event) {
|
||||||
|
// Some devices return null event on editor actions for Enter Button (thanks, Samsung)
|
||||||
|
if (!(event == null || event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Enter clicked on Bluetooth Keyboard (action is zero on hardware keyboards)
|
||||||
|
if (action == EditorInfo.IME_ACTION_UNSPECIFIED && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return action == EditorInfo.IME_ACTION_DONE
|
||||||
|
|| action == EditorInfo.IME_ACTION_GO
|
||||||
|
|| action == EditorInfo.IME_ACTION_SEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchesRequiredPINLength(String input) {
|
||||||
|
final PINInputView inputView = this.inputView.get();
|
||||||
|
|
||||||
|
if (inputView == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return inputView.matchesRequiredPINLength(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextEntered(int count) {
|
||||||
|
if (count == Integer.parseInt(AppLock.getPinCodeLimits(AndroidUtils.getGlobalContext()))) {
|
||||||
|
var inputView = this.inputView.get();
|
||||||
|
InputEventListener eventListener = this.eventListener.get();
|
||||||
|
|
||||||
|
if (eventListener != null)
|
||||||
|
eventListener.onInputEntered(inputView.getText());
|
||||||
|
|
||||||
|
inputView.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface InputEventListener {
|
||||||
|
void onInputEntered(String input);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package com.guardanis.applock.pin;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class PINInputView extends LinearLayout implements TextWatcher {
|
||||||
|
|
||||||
|
private final WeakHashMap<PINItemView, PINItemAnimator> animators = new WeakHashMap<PINItemView, PINItemAnimator>();
|
||||||
|
private Paint itemTextPaint;
|
||||||
|
private Paint itemBackgroundPaint;
|
||||||
|
private int inputViewsCount = 10;
|
||||||
|
private PINItemView[] pinItemViews;
|
||||||
|
private EditText editText;
|
||||||
|
private String lastText = "";
|
||||||
|
private boolean passwordCharactersEnabled = true;
|
||||||
|
private String passwordCharacter;
|
||||||
|
private InputTextListener listener;
|
||||||
|
|
||||||
|
|
||||||
|
public PINInputView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(AttributeSet attrs) {
|
||||||
|
setWillNotDraw(false);
|
||||||
|
setupEditText();
|
||||||
|
|
||||||
|
|
||||||
|
itemTextPaint = new Paint();
|
||||||
|
itemTextPaint.setColor(ThemesUtils.getAccentColor());
|
||||||
|
|
||||||
|
itemBackgroundPaint = new Paint();
|
||||||
|
itemBackgroundPaint.setColor(ThemesUtils.getMutedAccentColor());
|
||||||
|
|
||||||
|
|
||||||
|
inputViewsCount = Integer.parseInt(AppLock.getPinCodeLimits(AndroidUtils.getGlobalContext()));
|
||||||
|
|
||||||
|
passwordCharacter = getResources().getString(AndroidUtils.getIdentifier("applock__password_char", "string"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupEditText() {
|
||||||
|
removeAllViews();
|
||||||
|
|
||||||
|
this.editText = inflateFakeEditText();
|
||||||
|
this.editText.addTextChangedListener(this);
|
||||||
|
|
||||||
|
addView(editText);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EditText inflateFakeEditText() {
|
||||||
|
return (EditText) LayoutInflater.from(getContext())
|
||||||
|
.inflate(AndroidUtils.getIdentifier("applock__fake_edit_text", "layout"), this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputView setInputViewsCount(int inputViewsCount) {
|
||||||
|
this.inputViewsCount = inputViewsCount;
|
||||||
|
|
||||||
|
return reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputView reset() {
|
||||||
|
this.lastText = "";
|
||||||
|
this.editText.setText("");
|
||||||
|
|
||||||
|
if (pinItemViews != null)
|
||||||
|
animateLastOut();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
ensureKeyboardVisible();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ensureKeyboardVisible() {
|
||||||
|
editText.requestFocus();
|
||||||
|
|
||||||
|
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
|
||||||
|
.showSoftInput(editText, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas canvas) {
|
||||||
|
if (pinItemViews == null || pinItemViews.length != inputViewsCount)
|
||||||
|
setupItemViews(canvas);
|
||||||
|
|
||||||
|
String text = editText.getText().toString();
|
||||||
|
|
||||||
|
for (int i = 0; i < pinItemViews.length; i++) {
|
||||||
|
String textItem = i < text.length()
|
||||||
|
? (passwordCharactersEnabled ? passwordCharacter : text.substring(i, i + 1))
|
||||||
|
: "";
|
||||||
|
|
||||||
|
pinItemViews[i].draw(canvas, textItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupItemViews(Canvas canvas) {
|
||||||
|
pinItemViews = new PINItemView[inputViewsCount];
|
||||||
|
|
||||||
|
int cellWidth = canvas.getWidth() / inputViewsCount;
|
||||||
|
int largestRadius = Math.min(cellWidth / 2, canvas.getHeight() / 2);
|
||||||
|
int smallestRadius = (int) (largestRadius * Float.parseFloat(getResources().getString(AndroidUtils.getIdentifier("applock__empty_item_min_size_percent", "string"))));
|
||||||
|
int[] minMaxRadius = new int[]{smallestRadius, largestRadius};
|
||||||
|
|
||||||
|
itemTextPaint.setTextSize((int) (largestRadius * .85));
|
||||||
|
|
||||||
|
for (int i = 0; i < pinItemViews.length; i++) {
|
||||||
|
float[] positionInCanvas = getPositionInCanvas(canvas, i, cellWidth);
|
||||||
|
|
||||||
|
pinItemViews[i] = new PINItemView(positionInCanvas, minMaxRadius, itemTextPaint, itemBackgroundPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] getPositionInCanvas(Canvas canvas, int position, int cellWidth) {
|
||||||
|
return new float[]{
|
||||||
|
(cellWidth * position) + (cellWidth / 2),
|
||||||
|
canvas.getHeight() / 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable editable) {
|
||||||
|
if (editable == null || lastText == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String text = editable.toString();
|
||||||
|
if (inputViewsCount < text.length()) {
|
||||||
|
editText.setText(lastText);
|
||||||
|
editText.setSelection(editText.getText().length());
|
||||||
|
} else if (text.length() < lastText.length()) {
|
||||||
|
animateLastOut();
|
||||||
|
this.lastText = text;
|
||||||
|
} else if (lastText.length() < text.length()) {
|
||||||
|
animateLastIn();
|
||||||
|
this.lastText = text;
|
||||||
|
}
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onTextEntered(text.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputTextListener(InputTextListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateLastOut() {
|
||||||
|
int startingIndex = editText.getText().toString().length(); // One after current length, only happens on backspace
|
||||||
|
|
||||||
|
for (int i = pinItemViews.length - 1; startingIndex <= i; i--)
|
||||||
|
if (!pinItemViews[i].isAnimatedOut())
|
||||||
|
animate(pinItemViews[i], PINItemAnimator.ItemAnimationDirection.OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateLastIn() {
|
||||||
|
PINItemView item = pinItemViews[editText.getText().toString().length() - 1];
|
||||||
|
animate(item, PINItemAnimator.ItemAnimationDirection.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animate(PINItemView view, PINItemAnimator.ItemAnimationDirection direction) {
|
||||||
|
cancelPreviousAnimation(view);
|
||||||
|
view.setAnimationDirection(direction);
|
||||||
|
|
||||||
|
PINItemAnimator animator = new PINItemAnimator(this, view, direction);
|
||||||
|
animators.put(view, animator);
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelPreviousAnimation(PINItemView view) {
|
||||||
|
PINItemAnimator animator = animators.get(view);
|
||||||
|
|
||||||
|
try {
|
||||||
|
animator.cancel();
|
||||||
|
animators.put(view, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return editText.getText().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnEditorActionListener(TextView.OnEditorActionListener actionListener) {
|
||||||
|
editText.setOnEditorActionListener(actionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordCharactersEnabled(boolean passwordCharactersEnabled) {
|
||||||
|
this.passwordCharactersEnabled = passwordCharactersEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordCharacter(String passwordCharacter) {
|
||||||
|
this.passwordCharacter = passwordCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchesRequiredPINLength(String input) {
|
||||||
|
return input.length() == inputViewsCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.guardanis.applock.pin;
|
||||||
|
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class PINItemAnimator extends Thread {
|
||||||
|
|
||||||
|
private static final int ANIMATION_DURATION = 150;
|
||||||
|
private static final int UPDATE_RATE = 25;
|
||||||
|
private final WeakReference<PINInputView> inputView;
|
||||||
|
private final PINItemView itemView;
|
||||||
|
private final ItemAnimationDirection animationDirection;
|
||||||
|
private final Interpolator itemInterpolator = new AccelerateDecelerateInterpolator();
|
||||||
|
private final float minSizePercent;
|
||||||
|
private long startTime;
|
||||||
|
private boolean canceled = false;
|
||||||
|
|
||||||
|
public PINItemAnimator(PINInputView inputView, PINItemView itemView, ItemAnimationDirection animationDirection) {
|
||||||
|
this.inputView = new WeakReference<>(inputView);
|
||||||
|
this.itemView = itemView;
|
||||||
|
this.animationDirection = animationDirection;
|
||||||
|
this.minSizePercent = Float.parseFloat(inputView.getResources().getString(AndroidUtils.getIdentifier("applock__empty_item_min_size_percent", "string")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
this.startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (animationDirection == ItemAnimationDirection.IN)
|
||||||
|
animateIn();
|
||||||
|
else
|
||||||
|
animateOut();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateIn() throws Exception {
|
||||||
|
float percent = minSizePercent;
|
||||||
|
|
||||||
|
while (percent < 1 && !canceled) {
|
||||||
|
percent = Math.min(minSizePercent + calculatePercentComplete(), 1);
|
||||||
|
updateView(percent);
|
||||||
|
|
||||||
|
Thread.sleep(UPDATE_RATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateOut() throws Exception {
|
||||||
|
float percent = 1 - calculatePercentComplete();
|
||||||
|
|
||||||
|
while (minSizePercent < percent && !canceled) {
|
||||||
|
percent = Math.max(1 - calculatePercentComplete(), minSizePercent);
|
||||||
|
updateView(percent);
|
||||||
|
|
||||||
|
Thread.sleep(UPDATE_RATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float calculatePercentComplete() {
|
||||||
|
float completed = ((float) (System.currentTimeMillis() - startTime)) / ANIMATION_DURATION;
|
||||||
|
|
||||||
|
return itemInterpolator.getInterpolation(completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateView(final float percent) {
|
||||||
|
final PINInputView inputView = this.inputView.get();
|
||||||
|
|
||||||
|
if (inputView == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
inputView.post(() -> {
|
||||||
|
itemView.onAnimationUpdate(percent);
|
||||||
|
|
||||||
|
inputView.invalidate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
this.canceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ItemAnimationDirection {
|
||||||
|
IN, OUT
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.guardanis.applock.pin;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
public class PINItemView {
|
||||||
|
|
||||||
|
private final float[] position;
|
||||||
|
private final int intendedRadius;
|
||||||
|
private int currentRadius;
|
||||||
|
private Paint backgroundPaint;
|
||||||
|
|
||||||
|
private float[] textPosition;
|
||||||
|
private Paint textPaint;
|
||||||
|
|
||||||
|
private PINItemAnimator.ItemAnimationDirection animationDirection = PINItemAnimator.ItemAnimationDirection.IN;
|
||||||
|
|
||||||
|
public PINItemView(float[] position, int[] minMaxRadius, Paint baseTextPaint, Paint baseBackgroundPaint) {
|
||||||
|
this.position = position;
|
||||||
|
this.intendedRadius = minMaxRadius[1];
|
||||||
|
this.currentRadius = minMaxRadius[0];
|
||||||
|
|
||||||
|
setupPaints(baseTextPaint, baseBackgroundPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPaints(Paint baseTextPaint, Paint baseBackgroundPaint) {
|
||||||
|
this.textPaint = new Paint();
|
||||||
|
this.textPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
this.textPaint.setAntiAlias(true);
|
||||||
|
this.textPaint.setColor(baseTextPaint.getColor());
|
||||||
|
this.textPaint.setTextSize(baseTextPaint.getTextSize());
|
||||||
|
|
||||||
|
this.textPosition = new float[]{
|
||||||
|
position[0],
|
||||||
|
position[1] - ((textPaint.descent() + textPaint.ascent()) / 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.backgroundPaint = new Paint();
|
||||||
|
this.backgroundPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
this.backgroundPaint.setAntiAlias(true);
|
||||||
|
this.backgroundPaint.setColor(baseBackgroundPaint.getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Canvas canvas, String textValue) {
|
||||||
|
canvas.drawCircle(position[0], position[1], currentRadius, backgroundPaint);
|
||||||
|
canvas.drawText(textValue, textPosition[0], textPosition[1], textPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnimationDirection(PINItemAnimator.ItemAnimationDirection animationDirection) {
|
||||||
|
this.animationDirection = animationDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimationUpdate(float percentCompleted) {
|
||||||
|
this.currentRadius = (int) (intendedRadius * percentCompleted);
|
||||||
|
this.textPaint.setAlpha((int) (255 * percentCompleted));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnimatedOut() {
|
||||||
|
return animationDirection == PINItemAnimator.ItemAnimationDirection.OUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.guardanis.applock.services;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
|
import androidx.core.os.CancellationSignal;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.utils.CipherGenerator;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class FingerprintLockService extends LockService {
|
||||||
|
|
||||||
|
private static final String PREF_ENROLLMENT_ALLOWED = "pin__fingerprint_enrollment_allowed";
|
||||||
|
protected CancellationSignal fingerprintCancellationSignal;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnrollmentEligible(Context context) {
|
||||||
|
return Build.VERSION_CODES.M <= Build.VERSION.SDK_INT && context.getResources().getBoolean(AndroidUtils.getIdentifier("applock__fingerprint_service_enabled", "bool")) && isHardwarePresent(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enroll(Context context, AuthenticationDelegate delegate) {
|
||||||
|
authenticate(context, false, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticate(Context context, AuthenticationDelegate delegate) {
|
||||||
|
authenticate(context, true, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void authenticate(Context context, boolean localEnrollmentRequired, AuthenticationDelegate delegate) {
|
||||||
|
int errorCode = getRequiredResolutionErrorCode(context, localEnrollmentRequired);
|
||||||
|
|
||||||
|
if (-1 < errorCode) {
|
||||||
|
delegate.onResolutionRequired(errorCode);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be handled by getRequiredResolutionErrorCode(Context, boolean)
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
|
||||||
|
|
||||||
|
attemptFingerprintManagerAuthentication(context, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the resolvable error code or -1 if there are no issues requiring a resolution
|
||||||
|
*/
|
||||||
|
protected int getRequiredResolutionErrorCode(Context context, boolean localEnrollmentRequired) {
|
||||||
|
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||||
|
return AppLock.ERROR_CODE_SDK_VERSION_MINIMUM;
|
||||||
|
|
||||||
|
if (localEnrollmentRequired && !isEnrolled(context))
|
||||||
|
return AppLock.ERROR_CODE_FINGERPRINTS_NOT_LOCALLY_ENROLLED;
|
||||||
|
|
||||||
|
if (!isHardwarePresent(context)) return AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE;
|
||||||
|
|
||||||
|
if (!manager.hasEnrolledFingerprints()) return AppLock.ERROR_CODE_FINGERPRINTS_EMPTY;
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED)
|
||||||
|
return AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void attemptFingerprintManagerAuthentication(final Context context, final AuthenticationDelegate delegate) {
|
||||||
|
this.fingerprintCancellationSignal = new CancellationSignal();
|
||||||
|
|
||||||
|
|
||||||
|
var callback = new FingerprintManagerCompat.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||||
|
super.onAuthenticationError(errMsgId, errString);
|
||||||
|
|
||||||
|
delegate.onResolutionRequired(AndroidUtils.getIdentifier("applock__fingerprint_error_unknown", "string"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
|
||||||
|
super.onAuthenticationHelp(helpMsgId, helpString);
|
||||||
|
|
||||||
|
delegate.onAuthenticationHelp(helpMsgId, helpString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
|
||||||
|
super.onAuthenticationSucceeded(result);
|
||||||
|
|
||||||
|
notifyEnrolled(context);
|
||||||
|
|
||||||
|
delegate.onAuthenticationSuccess(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
delegate.onAuthenticating(fingerprintCancellationSignal);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Cipher cipher = generateAuthCipher(context, false, 0);
|
||||||
|
FingerprintManagerCompat.CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
|
||||||
|
|
||||||
|
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
|
||||||
|
manager.authenticate(cryptoObject, 0, fingerprintCancellationSignal, callback, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
delegate.onResolutionRequired(AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHardwarePresent(Context context) {
|
||||||
|
return FingerprintManagerCompat.from(context).isHardwareDetected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnrolled(Context context) {
|
||||||
|
return AppLock.getInstance(context).getPreferences().getBoolean(PREF_ENROLLMENT_ALLOWED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyEnrolled(Context context) {
|
||||||
|
AppLock.getInstance(context).getPreferences().edit().putBoolean(PREF_ENROLLMENT_ALLOWED, true).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
protected Cipher generateAuthCipher(Context context, boolean forceRegenerate, int attempts) throws Exception {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||||
|
throw new RuntimeException("generateAuthCipher() not supported before Api 23");
|
||||||
|
|
||||||
|
return new CipherGenerator().generateAuthCipher(context, forceRegenerate, attempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateEnrollments(Context context) {
|
||||||
|
AppLock.getInstance(context).getPreferences().edit().putBoolean(PREF_ENROLLMENT_ALLOWED, false).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelPendingAuthentications(Context context) {
|
||||||
|
if (fingerprintCancellationSignal != null) {
|
||||||
|
this.fingerprintCancellationSignal.cancel();
|
||||||
|
this.fingerprintCancellationSignal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AuthenticationDelegate {
|
||||||
|
void onResolutionRequired(int errorCode);
|
||||||
|
|
||||||
|
void onAuthenticationHelp(int code, CharSequence message);
|
||||||
|
|
||||||
|
void onAuthenticating(CancellationSignal cancellationSignal);
|
||||||
|
|
||||||
|
void onAuthenticationSuccess(FingerprintManagerCompat.AuthenticationResult result);
|
||||||
|
|
||||||
|
void onAuthenticationFailed(String message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.guardanis.applock.services;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public abstract class LockService {
|
||||||
|
|
||||||
|
public abstract boolean isEnrolled(Context context);
|
||||||
|
|
||||||
|
public abstract boolean isEnrollmentEligible(Context context);
|
||||||
|
|
||||||
|
public abstract void cancelPendingAuthentications(Context context);
|
||||||
|
|
||||||
|
public abstract void invalidateEnrollments(Context context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.guardanis.applock.services;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.utils.CryptoUtils;
|
||||||
|
|
||||||
|
public class PINLockService extends LockService {
|
||||||
|
|
||||||
|
private static final String PREF_SAVED_LOCKED_PASSWORD = "pin__saved_locked_password";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnrollmentEligible(Context context) {
|
||||||
|
// I couldn't find a good reason to allow the disabling of the PIN as an option.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticate(Context context, String pin, AuthenticationDelegate eventListener) {
|
||||||
|
if (!isEnrolled(context)) {
|
||||||
|
eventListener.onNoPIN();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getEnrolledPIN(context).equals(CryptoUtils.encryptSha1(pin))) {
|
||||||
|
eventListener.onPINDoesNotMatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventListener.onPINMatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnrolled(Context context) {
|
||||||
|
return AppLock.getInstance(context)
|
||||||
|
.getPreferences()
|
||||||
|
.getString(PREF_SAVED_LOCKED_PASSWORD, null) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getEnrolledPIN(Context context) {
|
||||||
|
return AppLock.getInstance(context)
|
||||||
|
.getPreferences()
|
||||||
|
.getString(PREF_SAVED_LOCKED_PASSWORD, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enroll(Context context, String pin) {
|
||||||
|
AppLock.getInstance(context)
|
||||||
|
.getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putString(PREF_SAVED_LOCKED_PASSWORD, CryptoUtils.encryptSha1(pin))
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateEnrollments(Context context) {
|
||||||
|
AppLock.getInstance(context)
|
||||||
|
.getPreferences()
|
||||||
|
.edit()
|
||||||
|
.remove(PREF_SAVED_LOCKED_PASSWORD)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelPendingAuthentications(Context context) {
|
||||||
|
// There are none
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AuthenticationDelegate {
|
||||||
|
void onNoPIN();
|
||||||
|
|
||||||
|
void onPINDoesNotMatch();
|
||||||
|
|
||||||
|
void onPINMatches();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.guardanis.applock.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
|
public class CipherGenerator {
|
||||||
|
|
||||||
|
private static final String KEYSTORE_NAME = "AndroidKeyStore";
|
||||||
|
|
||||||
|
public Cipher generateAuthCipher(Context context, boolean forceRegenerate, int attempts) throws Exception {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||||
|
throw new RuntimeException("generateAuthCipher() not supported before Api 23");
|
||||||
|
|
||||||
|
String alias = context.getString(AndroidUtils.getIdentifier("applock__fingerprint_alias", "string"));
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
|
||||||
|
keyStore.load(null);
|
||||||
|
|
||||||
|
if (forceRegenerate || !keyStore.containsAlias(alias)) {
|
||||||
|
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_NAME);
|
||||||
|
keyGenerator.init(new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||||
|
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||||
|
.setUserAuthenticationRequired(true)
|
||||||
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
keyGenerator.generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
String cipherFormat = String.format(
|
||||||
|
"%s/%s/%s",
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES,
|
||||||
|
KeyProperties.BLOCK_MODE_CBC,
|
||||||
|
KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(cipherFormat);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, keyStore.getKey(alias, null));
|
||||||
|
|
||||||
|
return cipher;
|
||||||
|
} catch (KeyPermanentlyInvalidatedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
if (1 < attempts)
|
||||||
|
return generateAuthCipher(context, true, attempts + 1);
|
||||||
|
} catch (UnrecoverableKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
if (1 < attempts)
|
||||||
|
return generateAuthCipher(context, true, attempts + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.guardanis.applock.utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
public class CryptoUtils {
|
||||||
|
|
||||||
|
public static String encryptSha1(String text) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||||
|
md.update(text.getBytes(StandardCharsets.UTF_8), 0, text.length());
|
||||||
|
|
||||||
|
return convertToHex(md.digest());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertToHex(byte[] data) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
|
||||||
|
for (byte b : data) {
|
||||||
|
int half = (b >>> 4) & 0x0F;
|
||||||
|
int twoHalves = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
buf.append(half <= 9
|
||||||
|
? (char) ('0' + half)
|
||||||
|
: (char) ('a' + half - 10));
|
||||||
|
|
||||||
|
half = b & 0x0F;
|
||||||
|
}
|
||||||
|
while (twoHalves++ < 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.guardanis.applock.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public class LifeCycleUtils {
|
||||||
|
|
||||||
|
public static AppLockActivityLifeCycleCallbacks attach(Activity activity, AppLockActivityLifeCycleCallbacks.Delegate delegate) {
|
||||||
|
AppLockActivityLifeCycleCallbacks callbacks = new AppLockActivityLifeCycleCallbacks(activity, delegate);
|
||||||
|
|
||||||
|
activity.getApplication()
|
||||||
|
.registerActivityLifecycleCallbacks(callbacks);
|
||||||
|
|
||||||
|
return callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AppLockActivityLifeCycleCallbacks implements Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
|
protected WeakReference<Activity> openedActivity;
|
||||||
|
protected WeakReference<Delegate> delegate;
|
||||||
|
|
||||||
|
public AppLockActivityLifeCycleCallbacks(Activity activity, Delegate delegate) {
|
||||||
|
this.openedActivity = new WeakReference<>(activity);
|
||||||
|
this.delegate = new WeakReference<>(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStarted(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStopped(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed(Activity activity) {
|
||||||
|
Activity opened = this.openedActivity.get();
|
||||||
|
Delegate delegate = this.delegate.get();
|
||||||
|
|
||||||
|
if (opened == null || delegate == null || opened != activity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
delegate.onActivityResumed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused(Activity activity) {
|
||||||
|
Activity opened = this.openedActivity.get();
|
||||||
|
Delegate delegate = this.delegate.get();
|
||||||
|
|
||||||
|
if (opened == null || delegate == null || opened != activity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
delegate.onActivityPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityDestroyed(Activity activity) {
|
||||||
|
Activity opened = this.openedActivity.get();
|
||||||
|
|
||||||
|
if (opened == null || opened != activity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
opened.getApplication()
|
||||||
|
.unregisterActivityLifecycleCallbacks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Delegate {
|
||||||
|
void onActivityResumed();
|
||||||
|
|
||||||
|
void onActivityPaused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package com.guardanis.applock.views;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.pin.PINInputController;
|
||||||
|
import com.guardanis.applock.pin.PINInputView;
|
||||||
|
import com.guardanis.applock.utils.LifeCycleUtils;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public abstract class AppLockViewController implements LifeCycleUtils.AppLockActivityLifeCycleCallbacks.Delegate {
|
||||||
|
|
||||||
|
protected PINInputController pinInputController;
|
||||||
|
|
||||||
|
protected WeakReference<Activity> activity;
|
||||||
|
protected WeakReference<View> parent;
|
||||||
|
|
||||||
|
protected WeakReference<PINInputView> pinInputView;
|
||||||
|
|
||||||
|
protected WeakReference<TextView> descriptionView;
|
||||||
|
protected WeakReference<View> actionSettings;
|
||||||
|
|
||||||
|
protected boolean autoAuthorizationEnabled = true;
|
||||||
|
|
||||||
|
protected Application.ActivityLifecycleCallbacks activityLifecycleCallbacks;
|
||||||
|
|
||||||
|
public AppLockViewController(Activity activity, View parent) {
|
||||||
|
this.activity = new WeakReference<>(activity);
|
||||||
|
this.parent = new WeakReference<>(parent);
|
||||||
|
this.descriptionView = new WeakReference<>(parent.findViewById(AndroidUtils.getIdentifier("pin__description", "id")));
|
||||||
|
this.actionSettings = new WeakReference<>(parent.findViewById(AndroidUtils.getIdentifier("pin__action_settings", "id")));
|
||||||
|
|
||||||
|
this.pinInputView = new WeakReference<>(parent.findViewById(AndroidUtils.getIdentifier("pin__input_view", "id")));
|
||||||
|
|
||||||
|
int inputViewsCount = Integer.parseInt(AppLock.getPinCodeLimits(activity));
|
||||||
|
|
||||||
|
boolean passwordCharsEnabled = parent.getResources()
|
||||||
|
.getBoolean(AndroidUtils.getIdentifier("applock__item_password_chars_enabled", "bool"));
|
||||||
|
|
||||||
|
this.pinInputController = new PINInputController(pinInputView.get())
|
||||||
|
.setInputNumbersCount(inputViewsCount)
|
||||||
|
.setPasswordCharactersEnabled(passwordCharsEnabled);
|
||||||
|
|
||||||
|
this.activityLifecycleCallbacks = LifeCycleUtils.attach(activity, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setupRootFlow();
|
||||||
|
|
||||||
|
public void setDescription(int descriptionResId) {
|
||||||
|
final TextView descriptionView = this.descriptionView.get();
|
||||||
|
|
||||||
|
if (descriptionView == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
descriptionView.setText(descriptionResId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
final TextView descriptionView = this.descriptionView.get();
|
||||||
|
|
||||||
|
if (descriptionView == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
descriptionView.setText(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends View> void hide(WeakReference<T> weakView) {
|
||||||
|
final T view = weakView.get();
|
||||||
|
|
||||||
|
if (view == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
view.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends View> void show(WeakReference<T> weakView) {
|
||||||
|
final T view = weakView.get();
|
||||||
|
|
||||||
|
if (view == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PINInputController getPINInputController() {
|
||||||
|
return pinInputController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getParent() {
|
||||||
|
return parent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoAuthorizationEnabled(boolean autoAuthorizationEnabled) {
|
||||||
|
this.autoAuthorizationEnabled = autoAuthorizationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateActionSettings(final int errorCode) {
|
||||||
|
View actionSettings = this.actionSettings.get();
|
||||||
|
|
||||||
|
if (actionSettings == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (getSettingsIntent(errorCode) == null) {
|
||||||
|
actionSettings.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionSettings.setVisibility(View.VISIBLE);
|
||||||
|
actionSettings.setOnClickListener(new View.OnClickListener() {
|
||||||
|
public void onClick(View view) {
|
||||||
|
handleActionSettingsClicked(errorCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void handleActionSettingsClicked(int errorCode);
|
||||||
|
|
||||||
|
protected void handleInitialErrorPrompt(int errorCode) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (errorCode == AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
new String[]{Manifest.permission.USE_FINGERPRINT},
|
||||||
|
AppLock.REQUEST_CODE_FINGERPRINT_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getDescriptionResIdForError(int errorCode) {
|
||||||
|
return switch (errorCode) {
|
||||||
|
case AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED ->
|
||||||
|
AndroidUtils.getIdentifier("applock__fingerprint_error_permission", "string");
|
||||||
|
case AppLock.ERROR_CODE_FINGERPRINTS_EMPTY ->
|
||||||
|
AndroidUtils.getIdentifier("applock__fingerprint_error_none", "string");
|
||||||
|
case AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE ->
|
||||||
|
AndroidUtils.getIdentifier("applock__fingerprint_error_hardware", "string");
|
||||||
|
case AppLock.ERROR_CODE_FINGERPRINTS_NOT_LOCALLY_ENROLLED ->
|
||||||
|
AndroidUtils.getIdentifier("applock__fingerprint_error_not_enrolled", "string");
|
||||||
|
default -> AndroidUtils.getIdentifier("applock__fingerprint_error_unknown", "string");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an Intent directed towards the correct system setting for the error, or null if there is none.
|
||||||
|
*/
|
||||||
|
public Intent getSettingsIntent(int errorCode) {
|
||||||
|
return switch (errorCode) {
|
||||||
|
case AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED, AppLock.ERROR_CODE_FINGERPRINTS_EMPTY, AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE ->
|
||||||
|
new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS);
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterReceivers() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activity.getApplication()
|
||||||
|
.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package com.guardanis.applock.views;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
|
import androidx.core.os.CancellationSignal;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.pin.PINInputController;
|
||||||
|
import com.guardanis.applock.services.FingerprintLockService;
|
||||||
|
import com.guardanis.applock.services.PINLockService;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class LockCreationViewController extends AppLockViewController
|
||||||
|
implements PINInputController.InputEventListener, FingerprintLockService.AuthenticationDelegate {
|
||||||
|
|
||||||
|
protected DisplayVariant displayVariant = DisplayVariant.NONE;
|
||||||
|
protected WeakReference<Delegate> delegate;
|
||||||
|
protected WeakReference<View> chooserParent;
|
||||||
|
protected String pinFirst;
|
||||||
|
|
||||||
|
public LockCreationViewController(Activity activity, View parent) {
|
||||||
|
super(activity, parent);
|
||||||
|
|
||||||
|
this.chooserParent = new WeakReference<>(parent.findViewById(AndroidUtils.getIdentifier("pin__create_chooser_items", "id")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockCreationViewController setDelegate(Delegate delegate) {
|
||||||
|
this.delegate = new WeakReference<>(delegate);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupRootFlow() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setupPINCreation();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupPINCreation() {
|
||||||
|
this.displayVariant = DisplayVariant.PIN_CREATION;
|
||||||
|
|
||||||
|
hide(chooserParent);
|
||||||
|
hide(actionSettings);
|
||||||
|
|
||||||
|
show(pinInputView);
|
||||||
|
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_create_pin", "string"));
|
||||||
|
|
||||||
|
pinInputController.ensureKeyboardVisible();
|
||||||
|
pinInputController.setInputEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupPINConfirmation() {
|
||||||
|
this.displayVariant = DisplayVariant.PIN_CONFIRMATION;
|
||||||
|
|
||||||
|
hide(chooserParent);
|
||||||
|
hide(actionSettings);
|
||||||
|
|
||||||
|
show(pinInputView);
|
||||||
|
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_confirm", "string"));
|
||||||
|
|
||||||
|
pinInputController.ensureKeyboardVisible();
|
||||||
|
pinInputController.setInputEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputEntered(String input) {
|
||||||
|
switch (displayVariant) {
|
||||||
|
case PIN_CREATION -> {
|
||||||
|
if (!pinInputController.matchesRequiredPINLength(input)) {
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__unlock_error_insufficient_selection", "string"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pinFirst = input;
|
||||||
|
setupPINConfirmation();
|
||||||
|
}
|
||||||
|
case PIN_CONFIRMATION -> {
|
||||||
|
if (!pinInputController.matchesRequiredPINLength(input)) {
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__unlock_error_insufficient_selection", "string"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!input.equals(pinFirst)) {
|
||||||
|
this.pinFirst = null;
|
||||||
|
|
||||||
|
setupPINCreation();
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_create_pin_reattempt", "string"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createPINLock(input);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createPINLock(String input) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.getLockService(PINLockService.class)
|
||||||
|
.enroll(activity, input);
|
||||||
|
|
||||||
|
var fingerprintLockService = AppLock.getInstance(activity).getLockService(FingerprintLockService.class);
|
||||||
|
if (fingerprintLockService.isEnrollmentEligible(activity)) {
|
||||||
|
setupFingerprintAuthentication();
|
||||||
|
} else {
|
||||||
|
handleLockCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupFingerprintAuthentication() {
|
||||||
|
this.displayVariant = DisplayVariant.FINGERPRINT_AUTHENTICATION;
|
||||||
|
|
||||||
|
hide(pinInputView);
|
||||||
|
hide(chooserParent);
|
||||||
|
hide(actionSettings);
|
||||||
|
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint", "string"));
|
||||||
|
|
||||||
|
if (autoAuthorizationEnabled)
|
||||||
|
attemptFingerprintAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void attemptFingerprintAuthentication() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.getLockService(FingerprintLockService.class)
|
||||||
|
.enroll(activity, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResolutionRequired(int errorCode) {
|
||||||
|
setDescription(getDescriptionResIdForError(errorCode));
|
||||||
|
updateActionSettings(errorCode);
|
||||||
|
handleInitialErrorPrompt(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationHelp(int code, CharSequence message) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String unformattedHelpMessage = activity.getString(AndroidUtils.getIdentifier("applock__description_unlock_fingerprint_help", "string"));
|
||||||
|
String formatted = String.format(unformattedHelpMessage, message);
|
||||||
|
|
||||||
|
setDescription(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticating(CancellationSignal cancellationSignal) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(FingerprintManagerCompat.AuthenticationResult result) {
|
||||||
|
handleLockCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailed(String message) {
|
||||||
|
setDescription(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleLockCreated() {
|
||||||
|
this.displayVariant = DisplayVariant.NONE;
|
||||||
|
|
||||||
|
Delegate delegate = this.delegate.get();
|
||||||
|
|
||||||
|
if (delegate != null)
|
||||||
|
delegate.onLockCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.cancelPendingAuthentications();
|
||||||
|
|
||||||
|
if (displayVariant == DisplayVariant.FINGERPRINT_AUTHENTICATION)
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint_paused", "string"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null || displayVariant != DisplayVariant.FINGERPRINT_AUTHENTICATION)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__fingerprint_error_permission_multiple", "string"));
|
||||||
|
updateActionSettings(AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint", "string"));
|
||||||
|
hide(actionSettings);
|
||||||
|
|
||||||
|
attemptFingerprintAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleActionSettingsClicked(int errorCode) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
Intent intent = getSettingsIntent(errorCode);
|
||||||
|
|
||||||
|
if (activity == null || intent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DisplayVariant {
|
||||||
|
NONE,
|
||||||
|
PIN_CREATION,
|
||||||
|
PIN_CONFIRMATION,
|
||||||
|
FINGERPRINT_AUTHENTICATION
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Delegate {
|
||||||
|
void onLockCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.guardanis.applock.views;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.pin.PINInputController;
|
||||||
|
import com.guardanis.applock.services.FingerprintLockService;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
public class UnlockViewController extends AppLockViewController implements AppLock.UnlockDelegate, PINInputController.InputEventListener {
|
||||||
|
|
||||||
|
protected DisplayVariant displayVariant = DisplayVariant.PIN_UNLOCK;
|
||||||
|
protected WeakReference<Delegate> delegate;
|
||||||
|
|
||||||
|
public UnlockViewController(Activity activity, View parent) {
|
||||||
|
super(activity, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnlockViewController setDelegate(Delegate delegate) {
|
||||||
|
this.delegate = new WeakReference<Delegate>(delegate);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupRootFlow() {
|
||||||
|
View parent = this.parent.get();
|
||||||
|
|
||||||
|
if (parent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setupPINUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupPINUnlock() {
|
||||||
|
this.displayVariant = DisplayVariant.PIN_UNLOCK;
|
||||||
|
|
||||||
|
hide(actionSettings);
|
||||||
|
show(pinInputView);
|
||||||
|
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_unlock_pin", "string"));
|
||||||
|
|
||||||
|
requestOpenKeyboard();
|
||||||
|
pinInputController.setInputEventListener(this);
|
||||||
|
|
||||||
|
FingerprintLockService fingerprintService = AppLock.getInstance(this.parent.get().getContext())
|
||||||
|
.getLockService(FingerprintLockService.class);
|
||||||
|
|
||||||
|
if (fingerprintService.isEnrolled(parent.get().getContext()))
|
||||||
|
attemptFingerprintAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputEntered(String input) {
|
||||||
|
if (!pinInputController.matchesRequiredPINLength(input)) {
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__unlock_error_insufficient_selection", "string"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptPINUnlock(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void attemptPINUnlock(String input) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.attemptPINUnlock(input, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void attemptFingerprintAuthentication() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.attemptFingerprintUnlock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnlockSuccessful() {
|
||||||
|
this.displayVariant = DisplayVariant.NONE;
|
||||||
|
|
||||||
|
Delegate delegate = this.delegate.get();
|
||||||
|
|
||||||
|
if (delegate != null)
|
||||||
|
delegate.onUnlockSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResolutionRequired(int errorCode) {
|
||||||
|
setDescription(getDescriptionResIdForError(errorCode));
|
||||||
|
updateActionSettings(errorCode);
|
||||||
|
handleInitialErrorPrompt(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationHelp(int code, String message) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String unformattedHelpMessage = activity.getString(AndroidUtils.getIdentifier("applock__description_unlock_fingerprint_help", "string"));
|
||||||
|
String formatted = String.format(unformattedHelpMessage, message);
|
||||||
|
|
||||||
|
setDescription(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailureLimitExceeded(String message) {
|
||||||
|
setDescription(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestOpenKeyboard() {
|
||||||
|
pinInputController.ensureKeyboardVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppLock.getInstance(activity)
|
||||||
|
.cancelPendingAuthentications();
|
||||||
|
|
||||||
|
if (displayVariant == DisplayVariant.FINGERPRINT_AUTHENTICATION)
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint_paused", "string"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed() {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
|
||||||
|
if (activity == null || displayVariant != DisplayVariant.FINGERPRINT_AUTHENTICATION)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__fingerprint_error_permission_multiple", "string"));
|
||||||
|
updateActionSettings(AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDescription(AndroidUtils.getIdentifier("applock__description_unlock_fingerprint", "string"));
|
||||||
|
hide(actionSettings);
|
||||||
|
|
||||||
|
attemptFingerprintAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleActionSettingsClicked(int errorCode) {
|
||||||
|
Activity activity = this.activity.get();
|
||||||
|
Intent intent = getSettingsIntent(errorCode);
|
||||||
|
|
||||||
|
if (activity == null || intent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DisplayVariant {
|
||||||
|
NONE,
|
||||||
|
PIN_UNLOCK,
|
||||||
|
FINGERPRINT_AUTHENTICATION
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Delegate {
|
||||||
|
void onUnlockSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
package com.vk.api.audio;
|
package com.vk.api.audio;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.vk.api.base.ApiRequest;
|
import com.vk.api.base.ApiRequest;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.log.L;
|
import com.vk.log.L;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.Genius;
|
import ru.vtosters.lite.music.Genius;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.music.Genius;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class AudioGetLyrics extends ApiRequest<AudioGetLyrics.a> {
|
public class AudioGetLyrics extends ApiRequest<AudioGetLyrics.a> {
|
||||||
private final MusicTrack musicTrack;
|
private final MusicTrack musicTrack;
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import com.vtosters.lite.R;
|
|||||||
import com.vtosters.lite.ViewUtils;
|
import com.vtosters.lite.ViewUtils;
|
||||||
import com.vtosters.lite.ui.MusicErrorViewHelper;
|
import com.vtosters.lite.ui.MusicErrorViewHelper;
|
||||||
import com.vtosters.lite.ui.holder.RecyclerHolder;
|
import com.vtosters.lite.ui.holder.RecyclerHolder;
|
||||||
import ru.vtosters.lite.music.Genius;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
|
import ru.vtosters.lite.music.Genius;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|||||||
@@ -2140,6 +2140,8 @@ public final class R {
|
|||||||
public static final int im_ic_msgdl = 0x7f040774; // 2130970484
|
public static final int im_ic_msgdl = 0x7f040774; // 2130970484
|
||||||
public static final int im_ic_msgreload = 0x7f040775; // 2130970485
|
public static final int im_ic_msgreload = 0x7f040775; // 2130970485
|
||||||
public static final int im_ic_stats = 0x7f040776; // 2130970486
|
public static final int im_ic_stats = 0x7f040776; // 2130970486
|
||||||
|
public static final int pinBackgroundColor = 0x7f040777; // 2130970487
|
||||||
|
public static final int pinTextColor = 0x7f040778; // 2130970488
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class bool {
|
public static final class bool {
|
||||||
@@ -2160,6 +2162,9 @@ public final class R {
|
|||||||
public static final int spb_default_reversed = 0x7f05000e; // 2131034126
|
public static final int spb_default_reversed = 0x7f05000e; // 2131034126
|
||||||
public static final int vk_is_tablet = 0x7f05000f; // 2131034127
|
public static final int vk_is_tablet = 0x7f05000f; // 2131034127
|
||||||
public static final int com_samsung_android_icon_container_has_icon_container = 0x7f050010; // 2131034128
|
public static final int com_samsung_android_icon_container_has_icon_container = 0x7f050010; // 2131034128
|
||||||
|
public static final int applock__fingerprint_service_enabled = 0x7f050011; // 2131034129
|
||||||
|
public static final int applock__item_password_chars_enabled = 0x7f050012; // 2131034130
|
||||||
|
public static final int applock__unlock_activity_return_allowed = 0x7f050013; // 2131034131
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class color {
|
public static final class color {
|
||||||
@@ -2968,6 +2973,13 @@ public final class R {
|
|||||||
public static final int yellow_light = 0x7f060323; // 2131100451
|
public static final int yellow_light = 0x7f060323; // 2131100451
|
||||||
public static final int yellow_sunflower = 0x7f060324; // 2131100452
|
public static final int yellow_sunflower = 0x7f060324; // 2131100452
|
||||||
public static final int iclauncher = 0x7f060325; // 2131100453
|
public static final int iclauncher = 0x7f060325; // 2131100453
|
||||||
|
public static final int applock__action_settings = 0x7f060327; // 2131100455
|
||||||
|
public static final int applock__activity_background = 0x7f060328; // 2131100456
|
||||||
|
public static final int applock__create_choose_option_bg = 0x7f060329; // 2131100457
|
||||||
|
public static final int applock__create_choose_option_bg_pressed = 0x7f06032a; // 2131100458
|
||||||
|
public static final int applock__description_text = 0x7f06032b; // 2131100459
|
||||||
|
public static final int applock__item_background = 0x7f06032c; // 2131100460
|
||||||
|
public static final int applock__item_text = 0x7f06032d; // 2131100461
|
||||||
public static final int abc_color_highlight_material = 0x7f060004; // 2131099652
|
public static final int abc_color_highlight_material = 0x7f060004; // 2131099652
|
||||||
public static final int alpha05 = 0x7f060326; // 2131100454
|
public static final int alpha05 = 0x7f060326; // 2131100454
|
||||||
}
|
}
|
||||||
@@ -3821,6 +3833,21 @@ public final class R {
|
|||||||
public static final int wrap_content = 0x7f07035b; // 2131166043
|
public static final int wrap_content = 0x7f07035b; // 2131166043
|
||||||
public static final int year_label_height = 0x7f07035c; // 2131166044
|
public static final int year_label_height = 0x7f07035c; // 2131166044
|
||||||
public static final int year_label_text_size = 0x7f07035d; // 2131166045
|
public static final int year_label_text_size = 0x7f07035d; // 2131166045
|
||||||
|
public static final int applock__action_settings = 0x7f07035e; // 2131166046
|
||||||
|
public static final int applock__action_settings_margin_top = 0x7f07035f; // 2131166047
|
||||||
|
public static final int applock__action_settings_padding = 0x7f070360; // 2131166048
|
||||||
|
public static final int applock__activity_padding = 0x7f070361; // 2131166049
|
||||||
|
public static final int applock__chooser_image_size = 0x7f070362; // 2131166050
|
||||||
|
public static final int applock__chooser_parent_padding_vertical = 0x7f070363; // 2131166051
|
||||||
|
public static final int applock__chooser_text_padding_top = 0x7f070364; // 2131166052
|
||||||
|
public static final int applock__content_padding_horizontal = 0x7f070365; // 2131166053
|
||||||
|
public static final int applock__content_padding_vertical = 0x7f070366; // 2131166054
|
||||||
|
public static final int applock__description_padding_bottom = 0x7f070367; // 2131166055
|
||||||
|
public static final int applock__description_text = 0x7f070368; // 2131166056
|
||||||
|
public static final int applock__fingerprint_scan_image_margin_top = 0x7f070369; // 2131166057
|
||||||
|
public static final int applock__fingerprint_scan_image_size = 0x7f07036a; // 2131166058
|
||||||
|
public static final int applock__item_margin_top = 0x7f07036b; // 2131166059
|
||||||
|
public static final int applock__item_size = 0x7f07036c; // 2131166060
|
||||||
public static final int vkim_article_blocked_bottom_margin = 0x7f070329; // 2131165993
|
public static final int vkim_article_blocked_bottom_margin = 0x7f070329; // 2131165993
|
||||||
public static final int vkim_article_blocked_ic_margin = 0x7f07032a; // 2131165994
|
public static final int vkim_article_blocked_ic_margin = 0x7f07032a; // 2131165994
|
||||||
public static final int vkim_article_blocked_ic_size = 0x7f07032b; // 2131165995
|
public static final int vkim_article_blocked_ic_size = 0x7f07032b; // 2131165995
|
||||||
@@ -4999,6 +5026,9 @@ public final class R {
|
|||||||
public static final int custom_thumb_selector = 0x7f080be9; // 2131233769
|
public static final int custom_thumb_selector = 0x7f080be9; // 2131233769
|
||||||
public static final int custom_track_selector = 0x7f080bea; // 2131233770
|
public static final int custom_track_selector = 0x7f080bea; // 2131233770
|
||||||
public static final int splash = 0x7f080bec; // 2131233772
|
public static final int splash = 0x7f080bec; // 2131233772
|
||||||
|
public static final int applock__chooser_option_bg = 0x7f080bee; // 2131233774
|
||||||
|
public static final int applock__fingerprint = 0x7f080bef; // 2131233775
|
||||||
|
public static final int applock__pin = 0x7f080bf0; // 2131233776
|
||||||
public static final int abc_control_background_material = 0x7f080032; // 2131230770
|
public static final int abc_control_background_material = 0x7f080032; // 2131230770
|
||||||
public static final int ic_action_close = 0x7f080322; // 2131231522
|
public static final int ic_action_close = 0x7f080322; // 2131231522
|
||||||
public static final int ic_camera_outline_28_new = 0x7f0803a0; // 2131231648
|
public static final int ic_camera_outline_28_new = 0x7f0803a0; // 2131231648
|
||||||
@@ -11062,6 +11092,12 @@ public final class R {
|
|||||||
public static final int color_preview_border = 0x7f0a1024; // 2131365924
|
public static final int color_preview_border = 0x7f0a1024; // 2131365924
|
||||||
public static final int account_backup_view = 0x7f0a1025; // 2131365925
|
public static final int account_backup_view = 0x7f0a1025; // 2131365925
|
||||||
public static final int copy_image = 0x7f0a1026; // 2131365926
|
public static final int copy_image = 0x7f0a1026; // 2131365926
|
||||||
|
public static final int applock__activity_lock_creation = 0x7f0a1027; // 2131365927
|
||||||
|
public static final int applock__activity_unlock = 0x7f0a1028; // 2131365928
|
||||||
|
public static final int pin__action_settings = 0x7f0a1029; // 2131365929
|
||||||
|
public static final int pin__container = 0x7f0a102a; // 2131365930
|
||||||
|
public static final int pin__description = 0x7f0a102b; // 2131365931
|
||||||
|
public static final int pin__input_view = 0x7f0a102c; // 2131365932
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class integer {
|
public static final class integer {
|
||||||
@@ -11108,6 +11144,10 @@ public final class R {
|
|||||||
public static final int status_bar_notification_info_maxnum = 0x7f0b002b; // 2131427371
|
public static final int status_bar_notification_info_maxnum = 0x7f0b002b; // 2131427371
|
||||||
public static final int vkim_activity_animation_duration = 0x7f0b002c; // 2131427372
|
public static final int vkim_activity_animation_duration = 0x7f0b002c; // 2131427372
|
||||||
public static final int vkim_playing_drawable_rect_count = 0x7f0b002d; // 2131427373
|
public static final int vkim_playing_drawable_rect_count = 0x7f0b002d; // 2131427373
|
||||||
|
public static final int applock__activity_lock_reenable_minutes = 0x7f0b002e; // 2131427374
|
||||||
|
public static final int applock__failure_retry_delay = 0x7f0b002f; // 2131427375
|
||||||
|
public static final int applock__input_pin_item_count = 0x7f0b0030; // 2131427376
|
||||||
|
public static final int applock__max_retry_count = 0x7f0b0031; // 2131427377
|
||||||
public static final int emoji_size_in_sprite = 0x7f0b0012; // 2131427346
|
public static final int emoji_size_in_sprite = 0x7f0b0012; // 2131427346
|
||||||
public static final int s1dip = 0x7f0b0027; // 2131427367
|
public static final int s1dip = 0x7f0b0027; // 2131427367
|
||||||
public static final int s2dip = 0x7f0b0028; // 2131427368
|
public static final int s2dip = 0x7f0b0028; // 2131427368
|
||||||
@@ -12883,6 +12923,11 @@ public final class R {
|
|||||||
public static final int wppreview = 0x7f0d06df; // 2131560159
|
public static final int wppreview = 0x7f0d06df; // 2131560159
|
||||||
public static final int write_bar_i = 0x7f0d06e0; // 2131560160
|
public static final int write_bar_i = 0x7f0d06e0; // 2131560160
|
||||||
public static final int color_picker_item = 0x7f0d06e1; // 2131560161
|
public static final int color_picker_item = 0x7f0d06e1; // 2131560161
|
||||||
|
public static final int applock__activity_lock_creation = 0x7f0d06e2; // 2131560162
|
||||||
|
public static final int applock__activity_unlock = 0x7f0d06e3; // 2131560163
|
||||||
|
public static final int applock__fake_edit_text = 0x7f0d06e4; // 2131560164
|
||||||
|
public static final int applock__lock_creation = 0x7f0d06e5; // 2131560165
|
||||||
|
public static final int applock__unlock = 0x7f0d06e6; // 2131560166
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class menu {
|
public static final class menu {
|
||||||
@@ -19658,12 +19703,43 @@ public final class R {
|
|||||||
public static final int vkx_integration_enabled_info = 0x7f12188a; // 2131892362
|
public static final int vkx_integration_enabled_info = 0x7f12188a; // 2131892362
|
||||||
public static final int whitelisted_ad_groups_stories_title = 0x7f12188b; // 2131892363
|
public static final int whitelisted_ad_groups_stories_title = 0x7f12188b; // 2131892363
|
||||||
public static final int writebar_ios_title = 0x7f12188c; // 2131892364
|
public static final int writebar_ios_title = 0x7f12188c; // 2131892364
|
||||||
public static final int cache_size = 0x7f12188d; // 2131892365
|
public static final int applock__action_settings = 0x7f12188d; // 2131892365
|
||||||
public static final int cache_size_summ = 0x7f12188e; // 2131892366
|
public static final int applock__create_chooser_option_fingerprint = 0x7f12188e; // 2131892366
|
||||||
public static final int clear_all_cache = 0x7f12188f; // 2131892367
|
public static final int applock__create_chooser_option_pin = 0x7f12188f; // 2131892367
|
||||||
public static final int invalidate_theme_cache_each_update_title = 0x7f121890; // 2131892368
|
public static final int applock__description_chooser = 0x7f121890; // 2131892368
|
||||||
public static final int invertFilters = 0x7f121891; // 2131892369
|
public static final int applock__description_confirm = 0x7f121891; // 2131892369
|
||||||
public static final int invertFilters_summ = 0x7f121892; // 2131892370
|
public static final int applock__description_create_fingerprint = 0x7f121892; // 2131892370
|
||||||
|
public static final int applock__description_create_fingerprint_paused = 0x7f121893; // 2131892371
|
||||||
|
public static final int applock__description_create_pin = 0x7f121894; // 2131892372
|
||||||
|
public static final int applock__description_create_pin_reattempt = 0x7f121895; // 2131892373
|
||||||
|
public static final int applock__description_unlock_fingerprint = 0x7f121896; // 2131892374
|
||||||
|
public static final int applock__description_unlock_fingerprint_help = 0x7f121897; // 2131892375
|
||||||
|
public static final int applock__description_unlock_pin = 0x7f121898; // 2131892376
|
||||||
|
public static final int applock__empty_item_min_size_percent = 0x7f121899; // 2131892377
|
||||||
|
public static final int applock__fingerprint_alias = 0x7f12189a; // 2131892378
|
||||||
|
public static final int applock__fingerprint_error_hardware = 0x7f12189b; // 2131892379
|
||||||
|
public static final int applock__fingerprint_error_none = 0x7f12189c; // 2131892380
|
||||||
|
public static final int applock__fingerprint_error_not_enrolled = 0x7f12189d; // 2131892381
|
||||||
|
public static final int applock__fingerprint_error_permission = 0x7f12189e; // 2131892382
|
||||||
|
public static final int applock__fingerprint_error_permission_multiple = 0x7f12189f; // 2131892383
|
||||||
|
public static final int applock__fingerprint_error_unknown = 0x7f1218a0; // 2131892384
|
||||||
|
public static final int applock__fingerprint_error_unrecognized = 0x7f1218a1; // 2131892385
|
||||||
|
public static final int applock__password_char = 0x7f1218a2; // 2131892386
|
||||||
|
public static final int applock__toast_unlock_required = 0x7f1218a3; // 2131892387
|
||||||
|
public static final int applock__unlock_error_insufficient_selection = 0x7f1218a4; // 2131892388
|
||||||
|
public static final int applock__unlock_error_match_failed = 0x7f1218a5; // 2131892389
|
||||||
|
public static final int applock__unlock_error_no_matching_pin_found = 0x7f1218a6; // 2131892390
|
||||||
|
public static final int applock__unlock_error_retry_limit_exceeded = 0x7f1218a7; // 2131892391
|
||||||
|
public static final int cache_size = 0x7f1218a8; // 2131892392
|
||||||
|
public static final int cache_size_summ = 0x7f1218a9; // 2131892393
|
||||||
|
public static final int clear_all_cache = 0x7f1218aa; // 2131892394
|
||||||
|
public static final int invalidate_theme_cache_each_update_title = 0x7f1218ab; // 2131892395
|
||||||
|
public static final int invertFilters = 0x7f1218ac; // 2131892396
|
||||||
|
public static final int invertFilters_summ = 0x7f1218ad; // 2131892397
|
||||||
|
public static final int invertcached_summ = 0x7f1218ae; // 2131892398
|
||||||
|
public static final int invertcached_title = 0x7f1218af; // 2131892399
|
||||||
|
public static final int pin_not_set = 0x7f1218b0; // 2131892400
|
||||||
|
public static final int pin_reset = 0x7f1218b1; // 2131892401
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class style {
|
public static final class style {
|
||||||
@@ -20799,6 +20875,11 @@ public final class R {
|
|||||||
public static final int VkAmoledTheme = 0x7f130472; // 2131952754
|
public static final int VkAmoledTheme = 0x7f130472; // 2131952754
|
||||||
public static final int VkMilkAmoledStyle = 0x7f130473; // 2131952755
|
public static final int VkMilkAmoledStyle = 0x7f130473; // 2131952755
|
||||||
public static final int VkMilkAmoledTheme = 0x7f130474; // 2131952756
|
public static final int VkMilkAmoledTheme = 0x7f130474; // 2131952756
|
||||||
|
public static final int applock__action_settings = 0x7f130487; // 2131952775
|
||||||
|
public static final int applock__content_parent = 0x7f130488; // 2131952776
|
||||||
|
public static final int applock__description = 0x7f130489; // 2131952777
|
||||||
|
public static final int applock__input_fingerprint_view = 0x7f13048a; // 2131952778
|
||||||
|
public static final int applock__input_pin_view = 0x7f13048b; // 2131952779
|
||||||
public static final int Base_V23_Theme_AppCompat = 0x7f13009d; // 2131951773
|
public static final int Base_V23_Theme_AppCompat = 0x7f13009d; // 2131951773
|
||||||
public static final int Base_V23_Theme_AppCompat_Light = 0x7f13009e; // 2131951774
|
public static final int Base_V23_Theme_AppCompat_Light = 0x7f13009e; // 2131951774
|
||||||
public static final int Platform_V25_AppCompat = 0x7f13017f; // 2131951999
|
public static final int Platform_V25_AppCompat = 0x7f13017f; // 2131951999
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package ru.vtosters.hooks;
|
|||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.catalog.inject.CatalogJsonInjector;
|
import ru.vtosters.hooks.music.injectors.CatalogJsonInjector;
|
||||||
|
|
||||||
public class CatalogsHook {
|
public class CatalogsHook {
|
||||||
public static JSONObject injectCatalogGetAudio(JSONObject json) {
|
public static JSONObject injectCatalogGetAudio(JSONObject json) {
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import android.content.Context;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import com.google.firebase.iid.FirebaseInstanceIdReceiver;
|
import com.google.firebase.iid.FirebaseInstanceIdReceiver;
|
||||||
import com.google.firebase.iid.FirebaseInstanceIdReceiver2;
|
import com.google.firebase.iid.FirebaseInstanceIdReceiver2;
|
||||||
|
import com.google.firebase.iid.FirebaseInstanceIdReceiver3;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class GmsHook {
|
public class GmsHook {
|
||||||
private static final boolean needToSpoof = !isGmsInstalled() && isFakeGmsInstalled();
|
private static final boolean needToSpoof = !isGmsInstalled() && (isFakeGmsInstalled() || isFakeGms2Installed());
|
||||||
|
|
||||||
public static boolean isGmsInstalled() {
|
public static boolean isGmsInstalled() {
|
||||||
try {
|
try {
|
||||||
@@ -30,8 +31,17 @@ public class GmsHook {
|
|||||||
}
|
}
|
||||||
} // Microg Google Market Services check
|
} // Microg Google Market Services check
|
||||||
|
|
||||||
|
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() {
|
public static boolean isAnyServicesInstalled() {
|
||||||
return isGmsInstalled() || isFakeGmsInstalled();
|
return isGmsInstalled() || isFakeGmsInstalled() || isFakeGms2Installed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void fixGapps() {
|
public static void fixGapps() {
|
||||||
@@ -45,7 +55,7 @@ public class GmsHook {
|
|||||||
} // Music channels fix
|
} // Music channels fix
|
||||||
|
|
||||||
public static String replaceGMSPackage(String str) {
|
public static String replaceGMSPackage(String str) {
|
||||||
return needToSpoof ? str.replaceAll("com.google", "com.mgoogle") : str;
|
return needToSpoof ? str.replaceAll("com.google", (isFakeGms2Installed() ? "app.revanced" : "com.mgoogle")) : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFirebaseInstanceIdReceiver() {
|
public static String getFirebaseInstanceIdReceiver() {
|
||||||
@@ -53,6 +63,6 @@ public class GmsHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Class getFirebaseInstanceIdReceiverClass() {
|
public static Class getFirebaseInstanceIdReceiverClass() {
|
||||||
return needToSpoof ? FirebaseInstanceIdReceiver2.class : FirebaseInstanceIdReceiver.class;
|
return needToSpoof ? (isFakeGms2Installed() ? FirebaseInstanceIdReceiver3.class : FirebaseInstanceIdReceiver2.class) : FirebaseInstanceIdReceiver.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.content.Intent;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import b.h.g.k.VKProgressDialog;
|
import b.h.g.k.VKProgressDialog;
|
||||||
import com.aefyr.tsg.g2.TelegramStickersService;
|
import com.aefyr.tsg.g2.TelegramStickersService;
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.activities.UnlockActivity;
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
@@ -14,7 +16,6 @@ import ru.vtosters.lite.concurrent.VTExecutors;
|
|||||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||||
import ru.vtosters.lite.ssfs.UsersList;
|
import ru.vtosters.lite.ssfs.UsersList;
|
||||||
import ru.vtosters.lite.themes.ThemesManager;
|
import ru.vtosters.lite.themes.ThemesManager;
|
||||||
import ru.vtosters.lite.ui.dialogs.DisableBattery;
|
|
||||||
import ru.vtosters.lite.ui.dialogs.InstallGMS;
|
import ru.vtosters.lite.ui.dialogs.InstallGMS;
|
||||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||||
import ru.vtosters.lite.ui.dialogs.Start;
|
import ru.vtosters.lite.ui.dialogs.Start;
|
||||||
@@ -48,9 +49,8 @@ public class MainActivityInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.isNewBuild()
|
if (Preferences.isNewBuild()
|
||||||
&&!ThemesUtils.isMonetTheme()
|
&& !ThemesUtils.isMonetTheme()
|
||||||
&&ThemesManager.canApplyCustomAccent())
|
&& ThemesManager.canApplyCustomAccent()) {
|
||||||
{
|
|
||||||
Preferences.updateBuildNumber();
|
Preferences.updateBuildNumber();
|
||||||
updateBinsAndTmpArchive(activity);
|
updateBinsAndTmpArchive(activity);
|
||||||
}
|
}
|
||||||
@@ -64,12 +64,18 @@ public class MainActivityInjector {
|
|||||||
|
|
||||||
Start.alert(activity);
|
Start.alert(activity);
|
||||||
InstallGMS.alert(activity);
|
InstallGMS.alert(activity);
|
||||||
DisableBattery.alert(activity);
|
|
||||||
// VKIDProtection.alert(activity);
|
// VKIDProtection.alert(activity);
|
||||||
//needs to show selected tgs pack count in settings after cold launch
|
//needs to show selected tgs pack count in settings after cold launch
|
||||||
TelegramStickersService.getInstance(activity);
|
TelegramStickersService.getInstance(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void applock(Activity activity) {
|
||||||
|
if (AppLock.isUnlockRequired(activity)) {
|
||||||
|
var intent = new Intent(activity, UnlockActivity.class);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void updateBinsAndTmpArchive(Activity activity) {
|
private static void updateBinsAndTmpArchive(Activity activity) {
|
||||||
final VKProgressDialog dialog = new VKProgressDialog(activity);
|
final VKProgressDialog dialog = new VKProgressDialog(activity);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package ru.vtosters.hooks;
|
|
||||||
|
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class MusicCacheFilesHook {
|
|
||||||
public static File getTrackFile(String trackId) {
|
|
||||||
return new File(FileCacheImplementation.getTrackFolder(trackId), "track.mp3");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTrackExist(String trackId) {
|
|
||||||
return CacheDatabaseDelegate.isCached(trackId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.vk.music.fragment.MusicFragment;
|
import com.vk.music.fragment.MusicFragment;
|
||||||
+14
-32
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
@@ -8,45 +8,29 @@ import com.vk.music.bottomsheets.a.MusicAction;
|
|||||||
import com.vk.music.common.MusicPlaybackLaunchContext;
|
import com.vk.music.common.MusicPlaybackLaunchContext;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.VKXUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
|
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
|
||||||
import static ru.vtosters.hooks.other.Preferences.milkshake;
|
import static ru.vtosters.hooks.other.Preferences.milkshake;
|
||||||
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isCached;
|
|
||||||
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isVkxCached;
|
|
||||||
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
||||||
|
|
||||||
public class MusicBottomSheetHook {
|
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;
|
if (musicTrack.F1()) return actions;
|
||||||
|
final var trackId = asId(musicTrack);
|
||||||
var trackid = asId(musicTrack);
|
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
||||||
|
|
||||||
if (isVkxInstalled()) {
|
|
||||||
actions.add(getPlayInVKXAction());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIntegrationEnabled()) {
|
if (isIntegrationEnabled()) {
|
||||||
if (isVkxCached(trackid)) {
|
if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction());
|
||||||
actions.add(getRemoveCacheTrackVkxAction());
|
else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction());
|
||||||
} else if (isNetworkConnected()) {
|
|
||||||
actions.add(addToCacheTrackVkxAction());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isCached(trackid)) {
|
if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction());
|
||||||
actions.add(getRemoveCacheTrackAction());
|
else if (isNetworkConnected()) actions.add(addToCacheTrackAction());
|
||||||
} else if (isNetworkConnected()) {
|
|
||||||
actions.add(addToCacheTrackAction());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
|
||||||
if (isNetworkConnected()) {
|
|
||||||
actions.add(downloadAsMp3Action());
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +38,7 @@ public class MusicBottomSheetHook {
|
|||||||
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
||||||
if (musicTrack.F1()) return actions;
|
if (musicTrack.F1()) return actions;
|
||||||
|
|
||||||
var trackid = asId(musicTrack);
|
if (MusicCacheImpl.isCachedTrack(musicTrack.y1())) {
|
||||||
|
|
||||||
if (isCached(trackid)) {
|
|
||||||
actions.add(new MusicAction(
|
actions.add(new MusicAction(
|
||||||
R.id.music_action_toggle_download,
|
R.id.music_action_toggle_download,
|
||||||
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
|
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
|
||||||
@@ -85,7 +67,7 @@ public class MusicBottomSheetHook {
|
|||||||
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
||||||
|
|
||||||
if (isIntegrationEnabled()) {
|
if (isIntegrationEnabled()) {
|
||||||
if (isVkxCached(playlist.a, playlist.b)) {
|
if (VKXUtils.isVkxCached(playlist.a, playlist.b)) {
|
||||||
actions.add(getRemoveCacheTrackVkxAction());
|
actions.add(getRemoveCacheTrackVkxAction());
|
||||||
} else if (isNetworkConnected()) {
|
} else if (isNetworkConnected()) {
|
||||||
actions.add(addToCacheTrackVkxAction());
|
actions.add(addToCacheTrackVkxAction());
|
||||||
@@ -124,7 +106,7 @@ public class MusicBottomSheetHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actionId == R.id.remove_from_cache) {
|
if (actionId == R.id.remove_from_cache) {
|
||||||
CacheDatabaseDelegate.removeTrackFromCache(asId(track));
|
MusicCacheImpl.removeTrack(asId(track));
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return true;
|
return true;
|
||||||
} else if (actionId == R.id.add_to_cache) {
|
} else if (actionId == R.id.add_to_cache) {
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MusicCacheFilesHook {
|
||||||
|
public static File getTrackFile(String trackId) {
|
||||||
|
return MusicCacheStorageUtils.getTrackFile(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isTrackExist(String trackId) {
|
||||||
|
return MusicCacheImpl.isCachedTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.AttrRes;
|
import androidx.annotation.AttrRes;
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.catalog.inject;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
@@ -12,7 +12,7 @@ import org.json.JSONException;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class CatalogJsonInjector {
|
|||||||
setDefaultAudioPage(oldItems, catalog);
|
setDefaultAudioPage(oldItems, catalog);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||||
var noPlaylists = !json.has("playlists");
|
var noPlaylists = !json.has("playlists");
|
||||||
|
|
||||||
if (noPlaylists) {
|
if (noPlaylists) {
|
||||||
@@ -91,7 +91,7 @@ public class CatalogJsonInjector {
|
|||||||
|
|
||||||
fixDailyMix(blocks);
|
fixDailyMix(blocks);
|
||||||
|
|
||||||
if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) {
|
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
|
||||||
return; // early return if not users catalog or no tracks or integration enabled
|
return; // early return if not users catalog or no tracks or integration enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -12,7 +12,7 @@ import io.reactivex.ObservableOnSubscribe;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ public class PlaylistInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean eligibleForOfflineCaching() {
|
public static boolean eligibleForOfflineCaching() {
|
||||||
return CacheDatabaseDelegate.hasTracks();
|
return !MusicCacheImpl.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.music.model.IpcPlayerModelImpl;
|
import com.vk.music.model.IpcPlayerModelImpl;
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
@@ -11,7 +11,7 @@ import io.reactivex.ObservableOnSubscribe;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
@@ -56,7 +56,7 @@ public class TracklistInjector {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!CacheDatabaseDelegate.hasTracks()) {
|
if (MusicCacheImpl.isEmpty()) {
|
||||||
observableEmitter.b(parser.c(getEmptyCatalog()));
|
observableEmitter.b(parser.c(getEmptyCatalog()));
|
||||||
} else {
|
} else {
|
||||||
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
||||||
@@ -135,7 +135,7 @@ public class TracklistInjector {
|
|||||||
|
|
||||||
button.put("action", action);
|
button.put("action", action);
|
||||||
button.put("block_id", "shuffle");
|
button.put("block_id", "shuffle");
|
||||||
button.put("ref_items_count", CacheDatabaseDelegate.getTrackCount());
|
button.put("ref_items_count", MusicCacheImpl.getTracksCount());
|
||||||
button.put("ref_layout_name", "list");
|
button.put("ref_layout_name", "list");
|
||||||
button.put("ref_data_type", "music_audios");
|
button.put("ref_data_type", "music_audios");
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ public class TracklistInjector {
|
|||||||
|
|
||||||
JSONArray blocks = firstSection.getJSONArray("blocks");
|
JSONArray blocks = firstSection.getJSONArray("blocks");
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||||
boolean noPlaylists = hasNoPlaylists(blocks);
|
boolean noPlaylists = hasNoPlaylists(blocks);
|
||||||
|
|
||||||
if (noPlaylists) {
|
if (noPlaylists) {
|
||||||
@@ -2,10 +2,13 @@ package ru.vtosters.hooks.other;
|
|||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.activities.UnlockActivity;
|
||||||
import com.vk.medianative.MediaImageEncoder;
|
import com.vk.medianative.MediaImageEncoder;
|
||||||
import com.vk.medianative.MediaNative;
|
import com.vk.medianative.MediaNative;
|
||||||
import com.vtosters.lite.data.Users;
|
import com.vtosters.lite.data.Users;
|
||||||
@@ -33,7 +36,15 @@ public class Preferences {
|
|||||||
VTVerifications.load(application);
|
VTVerifications.load(application);
|
||||||
LifecycleUtils.registerActivities(application);
|
LifecycleUtils.registerActivities(application);
|
||||||
|
|
||||||
|
if (AppLock.isEnrolled(AndroidUtils.getGlobalContext())) {
|
||||||
|
AppLock.setAuthenticationRequired(AndroidUtils.getGlobalContext());
|
||||||
|
}
|
||||||
|
|
||||||
AnalyticsHelper.start(application);
|
AnalyticsHelper.start(application);
|
||||||
|
|
||||||
|
if (AppLock.isEnrolled(AndroidUtils.getGlobalContext())) {
|
||||||
|
AppLock.setAuthenticationRequired(AndroidUtils.getGlobalContext());
|
||||||
|
}
|
||||||
} // VK Init
|
} // VK Init
|
||||||
|
|
||||||
public static String getBuildName() {
|
public static String getBuildName() {
|
||||||
@@ -263,7 +274,7 @@ public class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean stories() {
|
public static boolean stories() {
|
||||||
return getBoolValue("stories", true);
|
return getBoolValue("stories", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean swipe() {
|
public static boolean swipe() {
|
||||||
@@ -292,12 +303,12 @@ public class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNewBuild() {
|
public static boolean isNewBuild() {
|
||||||
if(Preferences.getBoolValue("invalidate_theme_cache_each_update",true))
|
if (Preferences.getBoolValue("invalidate_theme_cache_each_update", true))
|
||||||
try {
|
try {
|
||||||
return AndroidUtils.getGlobalContext()
|
return AndroidUtils.getGlobalContext()
|
||||||
.getPackageManager()
|
.getPackageManager()
|
||||||
.getPackageInfo(AndroidUtils.getPackageName(), 0)
|
.getPackageInfo(AndroidUtils.getPackageName(), 0)
|
||||||
.lastUpdateTime != getPreferences().getLong("setupTime", 0L);
|
.lastUpdateTime != getPreferences().getLong("setupTime", 0L);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import com.vk.core.util.ToastUtils;
|
|||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.dto.music.Playlist;
|
import com.vk.dto.music.Playlist;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.MusicCacheFilesHook;
|
import ru.vtosters.hooks.music.MusicCacheFilesHook;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
||||||
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
||||||
import ru.vtosters.lite.music.downloader.AudioGet;
|
import ru.vtosters.lite.music.downloader.AudioGet;
|
||||||
@@ -58,10 +58,10 @@ public class AudioDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track) {
|
public static void cacheTrack(MusicTrack track) {
|
||||||
var trackId = track.y1();
|
var trackId = LibVKXClient.asId(track);
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.isCached(trackId)) {
|
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
||||||
CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track));
|
MusicCacheImpl.removeTrack(trackId);
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import okhttp3.Request;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import androidx.annotation.Nullable;
|
|||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import okhttp3.Callback;
|
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -47,7 +46,7 @@ public class LastFMScrobbler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.autocache() && !CacheDatabaseDelegate.isCached(uid)) {
|
if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) {
|
||||||
if (LibVKXClient.isIntegrationEnabled()) {
|
if (LibVKXClient.isIntegrationEnabled()) {
|
||||||
LibVKXClient.getInstance().runOnService(service -> {
|
LibVKXClient.getInstance().runOnService(service -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import java8.util.concurrent.CompletableFuture;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.FFMpeg;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.TSMerger;
|
|
||||||
import ru.vtosters.lite.music.downloader.ThumbnailDownloader;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
public class M3UDownloader implements ITrackDownloader {
|
|
||||||
private static final OkHttpClient client = VtOkHttpClient.getInstance();
|
|
||||||
|
|
||||||
public static M3UDownloader getInstance() {
|
|
||||||
return Holder.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTitle(MusicTrack track) {
|
|
||||||
String title = null;
|
|
||||||
|
|
||||||
if (track.f != null) {
|
|
||||||
title = track.f;
|
|
||||||
if (!track.g.isEmpty()) {
|
|
||||||
title += " (" + track.g + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
Request request = new Request.a().b(track.D).a();
|
|
||||||
String response = client.a(request).execute().a().g();
|
|
||||||
parse(response, outDir, callback, track, cache);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
callback.onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parse(String payload, File outDir, Callback callback, MusicTrack track, boolean cache) throws UnsupportedEncodingException {
|
|
||||||
if (cache) downloadThumbnails(track);
|
|
||||||
|
|
||||||
VKM3UParser parser = new VKM3UParser(track.D.substring(0, track.D.lastIndexOf("/") + 1), payload);
|
|
||||||
AtomicInteger progress = new AtomicInteger(0);
|
|
||||||
List<CompletableFuture<Void>> futures = createFutures(parser, progress, callback, track);
|
|
||||||
|
|
||||||
var resultTs = getResultTsFile(track);
|
|
||||||
var resultMp3 = getResultMp3File(outDir, cache, track);
|
|
||||||
|
|
||||||
callback.onProgress(5);
|
|
||||||
var future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
|
||||||
future.join();
|
|
||||||
future.exceptionally(throwable -> {
|
|
||||||
callback.onFailure();
|
|
||||||
return null;
|
|
||||||
}).thenApply(v -> TSMerger.merge(getTsesDir(track), resultTs))
|
|
||||||
.thenApply(mergeResult -> mergeResult && FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track))
|
|
||||||
.thenApply(convertResult -> {
|
|
||||||
if (convertResult)
|
|
||||||
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / parser.mTransportStreams.size()));
|
|
||||||
else callback.onFailure();
|
|
||||||
return convertResult;
|
|
||||||
}).thenRun(() -> IOUtils.deleteRecursive(getTsesDir(track))).thenRun(() -> {
|
|
||||||
callback.onSuccess();
|
|
||||||
if (cache) CacheDatabaseDelegate.insertTrack(track);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadThumbnails(MusicTrack track) {
|
|
||||||
try {
|
|
||||||
ThumbnailDownloader.downloadThumbnails(track);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CompletableFuture<Void>> createFutures(VKM3UParser parser, AtomicInteger progress, Callback callback, MusicTrack track) {
|
|
||||||
List<CompletableFuture<Void>> futures = new ArrayList<>(parser.mTransportStreams.size());
|
|
||||||
|
|
||||||
var tses = parser.mTransportStreams;
|
|
||||||
for (var i = 0; i < tses.size(); ++i) {
|
|
||||||
final var ts = tses.get(i);
|
|
||||||
final var filename = i + ".ts";
|
|
||||||
futures.add(CompletableFuture.runAsync(() -> processTransportStream(parser, ts, filename, progress, tses.size(), callback, track)));
|
|
||||||
}
|
|
||||||
return futures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTransportStream(VKM3UParser parser, TransportStream ts, String filename, AtomicInteger progress, int size, Callback callback, MusicTrack track) {
|
|
||||||
try {
|
|
||||||
InputStream mediaIs = IOUtils.openStream(ts.getMediaSegmentUri());
|
|
||||||
byte[] content;
|
|
||||||
if (ts.needDecoding()) {
|
|
||||||
InputStream keyPubIs = IOUtils.openStream(ts.getKeyURL());
|
|
||||||
content = IOUtils.decodeStream(mediaIs, IOUtils.readFully(keyPubIs));
|
|
||||||
keyPubIs.close();
|
|
||||||
} else {
|
|
||||||
content = IOUtils.readFully(mediaIs);
|
|
||||||
}
|
|
||||||
File tsDump = new File(getTsesDir(track), filename);
|
|
||||||
IOUtils.writeToFile(tsDump, content);
|
|
||||||
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
|
|
||||||
callback.onSizeReceived((long) content.length * size, parser.mHeapSize);
|
|
||||||
mediaIs.close();
|
|
||||||
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException |
|
|
||||||
InvalidAlgorithmParameterException | InvalidKeyException e) {
|
|
||||||
callback.onFailure();
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getTsesDir(MusicTrack track) {
|
|
||||||
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
|
|
||||||
var tsesDir = new File(filesDir, String.valueOf(track.d));
|
|
||||||
tsesDir.mkdirs();
|
|
||||||
return tsesDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getResultTsFile(MusicTrack track) {
|
|
||||||
return new File(getTsesDir(track), "result.ts");
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
|
|
||||||
var fileName = IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3");
|
|
||||||
return new File(outDir, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialization-on-demand
|
|
||||||
private static class Holder {
|
|
||||||
private static final M3UDownloader INSTANCE = new M3UDownloader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
public class TransportStream {
|
|
||||||
|
|
||||||
private final String mMediaSegmentUri;
|
|
||||||
private String mKeyUri;
|
|
||||||
private String mMediaSegmentName;
|
|
||||||
|
|
||||||
public TransportStream(String baseURI, String mediaSegmentName) {
|
|
||||||
this.mMediaSegmentUri = baseURI + mediaSegmentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransportStream(String keyURI, String baseURI, String mediaSegmentName) {
|
|
||||||
this.mKeyUri = keyURI;
|
|
||||||
this.mMediaSegmentUri = baseURI + mediaSegmentName;
|
|
||||||
this.mMediaSegmentName = mediaSegmentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean needDecoding() {
|
|
||||||
return !TextUtils.isEmpty(mKeyUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyURL() {
|
|
||||||
return mKeyUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaSegmentUri() {
|
|
||||||
return this.mMediaSegmentUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaSegmentName() {
|
|
||||||
return this.mMediaSegmentName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parser for extended M3U playlist format used by VK.
|
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#ref-M3U">M3U</a>
|
|
||||||
**/
|
|
||||||
public class VKM3UParser {
|
|
||||||
//Playlist types
|
|
||||||
static public int PLAYLIST_TYPE_VOD = 0;
|
|
||||||
static public int PLAYLIST_TYPE_EVENT = 1;
|
|
||||||
private final String mData;
|
|
||||||
//region Extended M3U global extensions
|
|
||||||
public List<TransportStream> mTransportStreams = new ArrayList<>();
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.1.2">EXT-X-VERSION</a>
|
|
||||||
**/
|
|
||||||
public int mVersion = -1;
|
|
||||||
/**
|
|
||||||
* The EXT-X-ALLOW-CACHE tag is used in protocol version 6 or lower.
|
|
||||||
* If the protocol version is upgraded by VK, we will not support that.
|
|
||||||
**/
|
|
||||||
public boolean mAllowCache = false;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.1">EXT-X-TARGETDURATION</a>
|
|
||||||
**/
|
|
||||||
public int mTargetDuration = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.2">EXT-X-MEDIA-SEQUENCE</a>
|
|
||||||
**/
|
|
||||||
public int mMediaSequence = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.3">EXT-X-DISCONTINUITY-SEQUENCE</a>
|
|
||||||
**/
|
|
||||||
public int mDiscontinuitySequence = -1;
|
|
||||||
//endregion
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.5">EXT-X-PLAYLIST-TYPE</a>
|
|
||||||
**/
|
|
||||||
public int mPlaylistType = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.6">EXT-X-I-FRAMES-ONLY</a>
|
|
||||||
**/
|
|
||||||
public boolean mIFramesOnly = false;
|
|
||||||
public long mHeapSize = 0;
|
|
||||||
|
|
||||||
public VKM3UParser(String baseUri, String data) {
|
|
||||||
mData = data;
|
|
||||||
init(baseUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTS(String str) {
|
|
||||||
return str.matches(".+\\.ts.*");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(String baseUri) {
|
|
||||||
if (TextUtils.isEmpty(mData)) throw new NullPointerException("mData==null");
|
|
||||||
Scanner scanner = new Scanner(mData);
|
|
||||||
String line = scanner.nextLine();
|
|
||||||
if (!"#EXTM3U".equals(line))
|
|
||||||
throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line));
|
|
||||||
boolean aes128 = false;
|
|
||||||
String keyUri = "";
|
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
line = scanner.nextLine();
|
|
||||||
if ('#' == line.charAt(0)) {
|
|
||||||
parseTag(line);
|
|
||||||
if (line.startsWith("#EXT-X-KEY:") && line.contains("METHOD=AES-128")) {
|
|
||||||
aes128 = true;
|
|
||||||
keyUri = getKeyUri(line, baseUri);
|
|
||||||
}
|
|
||||||
} else if (isTS(line)) {
|
|
||||||
addTransportStream(aes128, keyUri, baseUri, line);
|
|
||||||
aes128 = false;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(String.format("Failed to parse: %s", line));
|
|
||||||
}
|
|
||||||
mHeapSize += line.getBytes().length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseTag(String line) {
|
|
||||||
if (line.startsWith("#EXT-X-VERSION:"))
|
|
||||||
mVersion = Integer.parseInt(line.substring(15));
|
|
||||||
else if (line.startsWith("#EXT-X-ALLOW-CACHE:"))
|
|
||||||
mAllowCache = "YES".equalsIgnoreCase(line.substring(19));
|
|
||||||
else if (line.startsWith("#EXT-X-TARGETDURATION:"))
|
|
||||||
mTargetDuration = Integer.parseInt(line.substring(22));
|
|
||||||
else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:"))
|
|
||||||
mMediaSequence = Integer.parseInt(line.substring(22));
|
|
||||||
else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE:"))
|
|
||||||
mDiscontinuitySequence = Integer.parseInt(line.substring(30));
|
|
||||||
else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:"))
|
|
||||||
parsePlaylistType(line.substring(21));
|
|
||||||
else if (line.startsWith("#EXT-X-I-FRAMES-ONLY"))
|
|
||||||
mIFramesOnly = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parsePlaylistType(String type) {
|
|
||||||
switch (type) {
|
|
||||||
case "VOD" -> mPlaylistType = PLAYLIST_TYPE_VOD;
|
|
||||||
case "EVENT" -> mPlaylistType = PLAYLIST_TYPE_EVENT;
|
|
||||||
default -> throw new IllegalStateException(String.format("Unknown playlist type: %s", type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getKeyUri(String line, String baseUri) {
|
|
||||||
String substr = line.substring(11);
|
|
||||||
substr = substr.substring(substr.indexOf('"') + 1, substr.lastIndexOf('"'));
|
|
||||||
//TODO: supposed endpoint
|
|
||||||
return substr.startsWith("http") ? substr : baseUri + substr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTransportStream(boolean aes128, String keyUri, String baseUri, String line) {
|
|
||||||
TransportStream ts = aes128
|
|
||||||
? new TransportStream(keyUri, baseUri, line)
|
|
||||||
: new TransportStream(baseUri, line);
|
|
||||||
mTransportStreams.add(ts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.DatabaseUtils;
|
|
||||||
import android.database.SQLException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ru.vtosters.lite.music.cache.FileCacheImplementation.getCacheDir;
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
|
|
||||||
import static ru.vtosters.lite.utils.IOUtils.deleteRecursive;
|
|
||||||
|
|
||||||
public class CacheDatabaseDelegate {
|
|
||||||
private static final int DB_VERSION = 0x1;
|
|
||||||
private static final String DB_NAME = "vt_lite_cache.db";
|
|
||||||
private static final String TABLE_NAME = "tracks";
|
|
||||||
private static final String COLUMN_ID = "id";
|
|
||||||
private static final String COLUMN_TRACK_ID = "trackId";
|
|
||||||
private static final String COLUMN_ALBUM_ID = "albumId";
|
|
||||||
private static final String COLUMN_TITLE = "title";
|
|
||||||
private static final String COLUMN_SUBTITLE = "subtitle";
|
|
||||||
private static final String COLUMN_ARTIST = "artist";
|
|
||||||
private static final String COLUMN_ALBUM_TITLE = "albumTitle";
|
|
||||||
private static final String COLUMN_EXPLICIT = "explicit";
|
|
||||||
private static final String COLUMN_HAS_ARTWORK = "hasArtwork";
|
|
||||||
private static final String COLUMN_DURATION = "duration";
|
|
||||||
private static DbHelper helperInstance;
|
|
||||||
|
|
||||||
protected static DbHelper getHelper() {
|
|
||||||
if (helperInstance == null) helperInstance = new DbHelper(getGlobalContext());
|
|
||||||
return helperInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// You MUST call this method ONLY AFTER TRACK IS DOWNLOADED!
|
|
||||||
public static void insertTrack(MusicTrack track) {
|
|
||||||
try {
|
|
||||||
var json = track.J();
|
|
||||||
var albumNode = json.optJSONObject("album");
|
|
||||||
|
|
||||||
var hasArtwork = false;
|
|
||||||
var albumId = -1;
|
|
||||||
var albumName = "";
|
|
||||||
|
|
||||||
if (albumNode != null) {
|
|
||||||
hasArtwork = albumNode.has("thumb");
|
|
||||||
albumId = albumNode.getInt("id");
|
|
||||||
albumName = albumNode.getString("title");
|
|
||||||
}
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES ('%s', %s, '%s', '%s', '%s', '%s', %s, %s, %s)",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK,
|
|
||||||
track.y1(),
|
|
||||||
albumId,
|
|
||||||
URLEncoder.encode(json.getString("title"), "UTF-8"),
|
|
||||||
URLEncoder.encode(json.optString("subtitle"), "UTF-8"),
|
|
||||||
URLEncoder.encode(json.getString("artist"), "UTF-8"),
|
|
||||||
URLEncoder.encode(albumName, "UTF-8"),
|
|
||||||
json.optInt("is_explicit"),
|
|
||||||
json.getInt("duration"),
|
|
||||||
hasArtwork ? 1 : 0
|
|
||||||
);
|
|
||||||
getHelper().getReadableDatabase().execSQL(formula);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
ToastUtils.a(AndroidUtils.getString(R.string.track_bd_insert_err) + " [" + e.getMessage() + "]", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void insertTracks(List<MusicTrack> tracks) {
|
|
||||||
for (var track : tracks) insertTrack(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
getHelper().getReadableDatabase().execSQL(String.format("DELETE FROM %s", TABLE_NAME));
|
|
||||||
deleteRecursive(getCacheDir());
|
|
||||||
sendToast(AndroidUtils.getString(R.string.cached_tracks_deleted));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static JSONObject reconstructFromDb(Cursor cursor) {
|
|
||||||
try {
|
|
||||||
var trackId = cursor.getString(getColumnIndex(cursor, COLUMN_TRACK_ID));
|
|
||||||
var splits = trackId.split("_");
|
|
||||||
var id = Integer.parseInt(splits[1]);
|
|
||||||
var ownerId = Integer.parseInt(splits[0]);
|
|
||||||
|
|
||||||
return new JSONObject()
|
|
||||||
.put("url", FileCacheImplementation.getServerPath())
|
|
||||||
.put("id", id)
|
|
||||||
.put("owner_id", ownerId)
|
|
||||||
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_TITLE)), "UTF-8"))
|
|
||||||
.put("subtitle", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_SUBTITLE)), "UTF-8"))
|
|
||||||
.put("artist", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ARTIST)), "UTF-8"))
|
|
||||||
.put("duration", cursor.getInt(getColumnIndex(cursor, COLUMN_DURATION)))
|
|
||||||
.put("is_explicit", cursor.getInt(getColumnIndex(cursor, COLUMN_EXPLICIT)))
|
|
||||||
.put("access_key", 2281488)
|
|
||||||
.putOpt("album", reconstructAlbumFromDb(cursor, trackId));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return new JSONObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static JSONObject reconstructAlbumFromDb(Cursor cursor, String trackId) {
|
|
||||||
try {
|
|
||||||
if (!FileCacheImplementation.getTrackThumbnail(trackId, 300).exists()
|
|
||||||
&& !FileCacheImplementation.getTrackThumbnail(trackId, 600).exists()
|
|
||||||
&& !FileCacheImplementation.getTrackThumbnail(trackId, 1200).exists()
|
|
||||||
) {
|
|
||||||
var ownerId = Integer.parseInt(trackId.split("_")[1]);
|
|
||||||
return new JSONObject()
|
|
||||||
.put("id", cursor.getInt(getColumnIndex(cursor, COLUMN_ALBUM_ID)))
|
|
||||||
.put("owner_id", ownerId)
|
|
||||||
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ALBUM_TITLE)), "UTF-8"))
|
|
||||||
.putOpt("thumb", reconstructAlbumThumbFromDb(cursor, trackId));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static JSONObject reconstructAlbumThumbFromDb(Cursor cursor, String trackId) {
|
|
||||||
try {
|
|
||||||
if (cursor.getInt(getColumnIndex(cursor, COLUMN_HAS_ARTWORK)) != 0)
|
|
||||||
new JSONObject()
|
|
||||||
.put("height", 600)
|
|
||||||
.put("width", 600)
|
|
||||||
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
|
|
||||||
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)))
|
|
||||||
.putOpt("photo_1200", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 1200)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getColumnIndex(Cursor cursor, String name) {
|
|
||||||
return cursor.getColumnIndexOrThrow(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasTracks() {
|
|
||||||
if (LibVKXClient.isIntegrationEnabled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME
|
|
||||||
);
|
|
||||||
var cursor = getHelper().getReadableDatabase().rawQuery(formula, null);
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
int count = cursor.getInt(0);
|
|
||||||
cursor.close();
|
|
||||||
return count > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks() {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s",
|
|
||||||
TABLE_NAME
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksAsPlaylist() {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s WHERE %s > -1 GROUP BY %s HAVING COUNT(*) > 1 ORDER by id DESC",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
COLUMN_ALBUM_ID
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksByAlbum(String id) {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s WHERE %s = %s",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksByAlbumOne(String id) {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s WHERE %s = %s LIMIT 1",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks(String query) {
|
|
||||||
List<MusicTrack> list = new ArrayList<>();
|
|
||||||
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(query, new String[0])) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
list.add(new MusicTrack(reconstructFromDb(cursor)));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCached(String trackId) {
|
|
||||||
var formula = String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s WHERE %s = '%s'",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID,
|
|
||||||
trackId
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
Cursor size = getHelper().getReadableDatabase().rawQuery(formula, null);
|
|
||||||
if (size != null) {
|
|
||||||
size.moveToFirst();
|
|
||||||
int count = size.getInt(0);
|
|
||||||
size.close();
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVkxCached(int playlist_id, int owner_id) {
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
|
||||||
@Override
|
|
||||||
public Boolean run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
return service.isPlaylistCached(playlist_id, owner_id);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean defaultValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVkxCached(String trackId) {
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
|
||||||
@Override
|
|
||||||
public Boolean run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
var splits = trackId.split("_");
|
|
||||||
var id = Integer.parseInt(splits[1]);
|
|
||||||
var owner = Integer.parseInt(splits[0]);
|
|
||||||
return service.isTrackCached(id, owner);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean defaultValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeTrackFromCache(String trackId) {
|
|
||||||
deleteRecursive(FileCacheImplementation.getTrackFolder(trackId));
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"DELETE FROM %s WHERE %s = '%s'",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID,
|
|
||||||
trackId
|
|
||||||
);
|
|
||||||
getHelper().getWritableDatabase().execSQL(formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getTrackCount() {
|
|
||||||
if (!LibVKXClient.isIntegrationEnabled())
|
|
||||||
return DatabaseUtils.queryNumEntries(getHelper().getReadableDatabase(), TABLE_NAME);
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
return (long) service.getCache().size();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return defaultValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long defaultValue() {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DbHelper extends SQLiteOpenHelper {
|
|
||||||
public DbHelper(Context context) {
|
|
||||||
super(context, DB_NAME, null, DB_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
var formula = String.format(
|
|
||||||
"CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT, %s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s INTEGER, %s INTEGER)",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ID, COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK
|
|
||||||
);
|
|
||||||
db.execSQL(formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import ru.vtosters.hooks.MusicCacheFilesHook;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class FileCacheImplementation {
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
CacheDatabaseDelegate.clear();
|
|
||||||
IOUtils.deleteRecursive(getCacheDir(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getCacheDir() {
|
|
||||||
return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackFolder(String trackId) {
|
|
||||||
var dir = new File(getCacheDir(), trackId);
|
|
||||||
dir.mkdirs();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getThumbnailsFolder(String trackId) {
|
|
||||||
var dir = new File(getTrackFolder(trackId), "thumbs");
|
|
||||||
dir.mkdirs();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackFile(MusicTrack track) {
|
|
||||||
return MusicCacheFilesHook.getTrackFile(track.y1());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackThumbnail(String trackId, int factor) {
|
|
||||||
return new File(getThumbnailsFolder(trackId), "thumb_" + factor + ".png");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFileUri(File file) {
|
|
||||||
if (!file.exists()) return null;
|
|
||||||
return Uri.fromFile(file).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getServerPath() {
|
|
||||||
return "https://vtosters.app";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class MusicCacheDbDelegate {
|
||||||
|
static synchronized private MusicCacheDb connectToDb(Context context) {
|
||||||
|
synchronized (MusicCacheDb.class) {
|
||||||
|
return new MusicCacheDb(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addTrack(Context context,
|
||||||
|
String trackId,
|
||||||
|
String albumId,
|
||||||
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
String artist,
|
||||||
|
String albumTitle,
|
||||||
|
boolean explicit,
|
||||||
|
int duration,
|
||||||
|
boolean hasArtwork) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
db.addTrack(
|
||||||
|
trackId,
|
||||||
|
albumId,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
artist,
|
||||||
|
albumTitle,
|
||||||
|
explicit,
|
||||||
|
duration,
|
||||||
|
hasArtwork);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeTrack(Context context, String trackId) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
db.deleteTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAllTracks(Context context) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getTracksCount(Context context) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.getTracksCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCachedTrack(Context context, String trackId) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.isCachedTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void drop(Context context) {
|
||||||
|
context.deleteDatabase(MusicCacheDb.Constants.DB_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package ru.vtosters.lite.music.cache;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MusicCacheImpl {
|
||||||
|
public static void addTrack(MusicTrack track) {
|
||||||
|
MusicCacheDbDelegate.addTrack(
|
||||||
|
AndroidUtils.getGlobalContext(),
|
||||||
|
track.y1(),
|
||||||
|
track.I != null ? track.I.getId() + "" : "-1",
|
||||||
|
track.f,
|
||||||
|
track.g,
|
||||||
|
track.C,
|
||||||
|
track.I != null ? track.I.getTitle() : "",
|
||||||
|
track.K,
|
||||||
|
track.h,
|
||||||
|
track.I != null && track.I.u1() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeTrack(String trackId) {
|
||||||
|
MusicCacheDbDelegate.removeTrack(AndroidUtils.getGlobalContext(), trackId);
|
||||||
|
MusicCacheStorageUtils.removeTrackDirById(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAllTracks() {
|
||||||
|
return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAlbumById(String albumId) {
|
||||||
|
return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getFirstAlbumTrack(String albumId) {
|
||||||
|
return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getPlaylist() {
|
||||||
|
return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getTracksCount() {
|
||||||
|
return !LibVKXClient.isIntegrationEnabled()
|
||||||
|
? MusicCacheDbDelegate.getTracksCount(AndroidUtils.getGlobalContext())
|
||||||
|
: LibVKXClient.getInstance().runOnServiceSync(
|
||||||
|
new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
|
||||||
|
@Override
|
||||||
|
public Long run(ILibVkxService service) {
|
||||||
|
try {
|
||||||
|
return (long) service.getCache().size();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return defaultValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long defaultValue() {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCachedTrack(String trackId) {
|
||||||
|
return MusicCacheDbDelegate.isCachedTrack(AndroidUtils.getGlobalContext(), trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEmpty() {
|
||||||
|
return !LibVKXClient.isIntegrationEnabled() && getTracksCount() == 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
|
||||||
|
MusicCacheStorageUtils.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package ru.vtosters.lite.music.cache.db;
|
||||||
|
|
||||||
|
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.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.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <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>
|
||||||
|
*/
|
||||||
|
public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //for pre-Q
|
||||||
|
public MusicCacheDb(Context context) {
|
||||||
|
super(context, Constants.DB_NAME, null, Constants.DV_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MusicTrack fromCursor(Cursor cur) {
|
||||||
|
try {
|
||||||
|
int trackIdIndex = cur.getColumnIndex(Constants.COLUMN_TRACK_ID);
|
||||||
|
int titleIndex = cur.getColumnIndex(Constants.COLUMN_TITLE);
|
||||||
|
int subtitleIndex = cur.getColumnIndex(Constants.COLUMN_SUBTITLE);
|
||||||
|
int artistIndex = cur.getColumnIndex(Constants.COLUMN_ARTIST);
|
||||||
|
int durationIndex = cur.getColumnIndex(Constants.COLUMN_DURATION);
|
||||||
|
int explicitIndex = cur.getColumnIndex(Constants.COLUMN_EXPLICIT);
|
||||||
|
int albumIdIndex = cur.getColumnIndex(Constants.COLUMN_ALBUM_ID);
|
||||||
|
int albumTitleIndex = cur.getColumnIndex(Constants.COLUMN_ALBUM_TITLE);
|
||||||
|
|
||||||
|
if (trackIdIndex == -1 || titleIndex == -1 || subtitleIndex == -1 || artistIndex == -1 || durationIndex == -1 || explicitIndex == -1 || albumIdIndex == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String trackId = cur.getString(trackIdIndex);
|
||||||
|
String[] splits = trackId.split("_");
|
||||||
|
|
||||||
|
if (splits.length != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicTrack res = new MusicTrack();
|
||||||
|
res.J = Integer.MAX_VALUE + ""; //access_key
|
||||||
|
res.D = "https://vtosters.app"; //url
|
||||||
|
res.d = Integer.parseInt(splits[1]); //id
|
||||||
|
res.e = Integer.parseInt(splits[0]); //owner_id
|
||||||
|
res.f = URLDecoder.decode(cur.getString(titleIndex), "UTF-8"); //title
|
||||||
|
res.g = URLDecoder.decode(cur.getString(subtitleIndex), "UTF-8"); //subtitle
|
||||||
|
res.C = URLDecoder.decode(cur.getString(artistIndex), "UTF-8"); //artist
|
||||||
|
res.h = cur.getInt(durationIndex); //duration
|
||||||
|
res.K = cur.getInt(explicitIndex) > 0; //is_explicit
|
||||||
|
|
||||||
|
int albumId = Integer.parseInt(cur.getString(albumIdIndex));
|
||||||
|
String albumTitle = albumTitleIndex != -1 ? cur.getString(albumTitleIndex) : null;
|
||||||
|
|
||||||
|
JSONObject node = new JSONObject();
|
||||||
|
|
||||||
|
File f300 = MusicCacheStorageUtils.getTrackThumb(trackId, 300);
|
||||||
|
File f600 = MusicCacheStorageUtils.getTrackThumb(trackId, 600);
|
||||||
|
File f1200 = MusicCacheStorageUtils.getTrackThumb(trackId, 1200);
|
||||||
|
|
||||||
|
if (f300.exists()) node.putOpt("photo_300", Uri.fromFile(f300).toString());
|
||||||
|
if (f600.exists()) node.putOpt("photo_600", Uri.fromFile(f600).toString());
|
||||||
|
if (f1200.exists()) node.putOpt("photo_1200", Uri.fromFile(f1200).toString());
|
||||||
|
|
||||||
|
res.I = new AlbumLink(
|
||||||
|
albumId,
|
||||||
|
res.e,
|
||||||
|
res.J,
|
||||||
|
albumTitle,
|
||||||
|
node.length() > 0
|
||||||
|
? new Thumb(node.put("width", 600)
|
||||||
|
.put("height", 600))
|
||||||
|
: null
|
||||||
|
); //album
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(Constants.CREATE_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
if (oldVersion < newVersion) {
|
||||||
|
List<MusicTrack> tracks = getTracksWithCursor(db.query(Constants.TABLE_NAME, null, null, null, null, null, null));
|
||||||
|
|
||||||
|
db.execSQL(Constants.DROP_QUERY);
|
||||||
|
|
||||||
|
onCreate(db);
|
||||||
|
|
||||||
|
for (MusicTrack track : tracks) {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(Constants.COLUMN_TRACK_ID, track.y1());
|
||||||
|
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, track.C);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrack(String trackId, String albumId, String title, String subtitle, String artist, String albumTitle, boolean explicit, int duration, boolean hasArtwork) {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(Constants.COLUMN_TRACK_ID, trackId);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_ID, albumId);
|
||||||
|
vals.put(Constants.COLUMN_TITLE, title);
|
||||||
|
vals.put(Constants.COLUMN_SUBTITLE, subtitle);
|
||||||
|
vals.put(Constants.COLUMN_ARTIST, artist);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_TITLE, albumTitle);
|
||||||
|
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(explicit, true));
|
||||||
|
vals.put(Constants.COLUMN_DURATION, duration);
|
||||||
|
vals.put(Constants.COLUMN_HAS_ARTWORK, Boolean.compare(hasArtwork, true));
|
||||||
|
|
||||||
|
long row = getWritableDatabase().insert(Constants.TABLE_NAME, null, vals);
|
||||||
|
|
||||||
|
if (AndroidUtils.isDebuggable()) Log.d("MusicCacheDb", "addTrack(): " + row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTrack(String trackId) {
|
||||||
|
getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCachedTrack(String trackId) {
|
||||||
|
try (var cur = getReadableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
new String[]{Constants.COLUMN_TRACK_ID},
|
||||||
|
Constants.COLUMN_TRACK_ID + " = ?",
|
||||||
|
new String[]{trackId},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"0,1")) {
|
||||||
|
return cur.moveToFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTracksCount() {
|
||||||
|
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID + " = ?",
|
||||||
|
new String[]{albumId},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MusicTrack> getPlaylist() {
|
||||||
|
return getTracksWithCursor(getWritableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID + " > -1",
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID,
|
||||||
|
"count(*) > 1",
|
||||||
|
Constants.COLUMN_ID + " desc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MusicTrack> getTracksWithCursor(Cursor cur) {
|
||||||
|
List<MusicTrack> res = new ArrayList<>();
|
||||||
|
if (cur != null && cur.moveToFirst()) {
|
||||||
|
try (cur) {
|
||||||
|
do {
|
||||||
|
MusicTrack track = fromCursor(cur);
|
||||||
|
if (track != null) res.add(track);
|
||||||
|
} while (cur.moveToNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Constants {
|
||||||
|
int DV_VERSION = 0x2;
|
||||||
|
String DB_NAME = "vt_lite_cache.db";
|
||||||
|
String TABLE_NAME = "tracks";
|
||||||
|
//columns
|
||||||
|
String COLUMN_ID = "id";
|
||||||
|
String COLUMN_TRACK_ID = "trackId";
|
||||||
|
String COLUMN_ALBUM_ID = "albumId";
|
||||||
|
String COLUMN_TITLE = "title";
|
||||||
|
String COLUMN_SUBTITLE = "subtitle";
|
||||||
|
String COLUMN_ARTIST = "artist";
|
||||||
|
String COLUMN_ALBUM_TITLE = "albumTitle";
|
||||||
|
String COLUMN_EXPLICIT = "explicit";
|
||||||
|
String COLUMN_DURATION = "duration";
|
||||||
|
String COLUMN_HAS_ARTWORK = "hasArtwork";
|
||||||
|
//queries
|
||||||
|
String CREATE_QUERY = "create table " + TABLE_NAME + "("
|
||||||
|
+ COLUMN_ID + " INTEGER PRIMARY KEY autoincrement,"
|
||||||
|
+ COLUMN_TRACK_ID + " text not null,"
|
||||||
|
+ COLUMN_ALBUM_ID + " text not null,"
|
||||||
|
+ COLUMN_TITLE + " text not null,"
|
||||||
|
+ COLUMN_SUBTITLE + " text not null,"
|
||||||
|
+ COLUMN_ARTIST + " text not null,"
|
||||||
|
+ COLUMN_ALBUM_TITLE + " text not null,"
|
||||||
|
+ COLUMN_EXPLICIT + " int not null,"
|
||||||
|
+ COLUMN_DURATION + " int not null,"
|
||||||
|
+ COLUMN_HAS_ARTWORK + " int not null"
|
||||||
|
+ ")";
|
||||||
|
String DROP_QUERY = "drop table if exists " + TABLE_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
package ru.vtosters.lite.music.cache.helpers;
|
package ru.vtosters.lite.music.cache.helpers;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import com.vk.dto.music.Playlist;
|
import com.vk.dto.music.Playlist;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -44,8 +45,8 @@ public class PlaylistHelper {
|
|||||||
if (!photo.isEmpty())
|
if (!photo.isEmpty())
|
||||||
jPhoto.put("height", 600)
|
jPhoto.put("height", 600)
|
||||||
.put("width", 600)
|
.put("width", 600)
|
||||||
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
|
.putOpt("photo_300", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 300)).toString())
|
||||||
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)));
|
.putOpt("photo_600", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 600)).toString());
|
||||||
var obj = new JSONObject()
|
var obj = new JSONObject()
|
||||||
.put("id", -2)
|
.put("id", -2)
|
||||||
.put("owner_id", albumId)
|
.put("owner_id", albumId)
|
||||||
@@ -68,7 +69,7 @@ public class PlaylistHelper {
|
|||||||
|
|
||||||
public static List<Playlist> getAlbumPlaylists() {
|
public static List<Playlist> getAlbumPlaylists() {
|
||||||
List<Playlist> list = new ArrayList<>();
|
List<Playlist> list = new ArrayList<>();
|
||||||
for (var track : CacheDatabaseDelegate.getTracksAsPlaylist()) {
|
for (var track : MusicCacheImpl.getPlaylist()) {
|
||||||
var albumLink = track.I;
|
var albumLink = track.I;
|
||||||
var thumb = albumLink.u1();
|
var thumb = albumLink.u1();
|
||||||
list.add(createAlbum(
|
list.add(createAlbum(
|
||||||
|
|||||||
+7
-7
@@ -7,8 +7,8 @@ import org.json.JSONArray;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@@ -22,7 +22,7 @@ public class TracklistHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks() {
|
public static List<MusicTrack> getTracks() {
|
||||||
List<MusicTrack> tracks = CacheDatabaseDelegate.getTracks();
|
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
|
||||||
|
|
||||||
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
|
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
|
||||||
|
|
||||||
@@ -35,11 +35,11 @@ public class TracklistHelper {
|
|||||||
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks(String id) {
|
public static List<MusicTrack> getTracks(String id) {
|
||||||
return CacheDatabaseDelegate.getTracksByAlbum(id);
|
return MusicCacheImpl.getAlbumById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksOne(String id) {
|
public static List<MusicTrack> getTracksOne(String id) {
|
||||||
return CacheDatabaseDelegate.getTracksByAlbumOne(id);
|
return MusicCacheImpl.getFirstAlbumTrack(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
||||||
@@ -56,7 +56,7 @@ public class TracklistHelper {
|
|||||||
var arr = new JSONArray();
|
var arr = new JSONArray();
|
||||||
for (MusicTrack track : tracks) {
|
for (MusicTrack track : tracks) {
|
||||||
var json = track.J();
|
var json = track.J();
|
||||||
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
|
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
|
||||||
try {
|
try {
|
||||||
addCachedThumbnails(json, folder);
|
addCachedThumbnails(json, folder);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
@@ -71,7 +71,7 @@ public class TracklistHelper {
|
|||||||
List<MusicTrack> tracks = new ArrayList<>();
|
List<MusicTrack> tracks = new ArrayList<>();
|
||||||
for (MusicTrack track : list) {
|
for (MusicTrack track : list) {
|
||||||
var json = track.J();
|
var json = track.J();
|
||||||
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
|
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
|
||||||
try {
|
try {
|
||||||
addCachedThumbnails(json, folder);
|
addCachedThumbnails(json, folder);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class MusicCallbackBuilder {
|
public class MusicCallbackBuilder {
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
package ru.vtosters.lite.music.converter.ts;
|
package ru.vtosters.lite.music.converter.ts;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.arthenica.ffmpegkit.FFmpegKit;
|
import com.arthenica.ffmpegkit.FFmpegKit;
|
||||||
import com.arthenica.ffmpegkit.ReturnCode;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
|
||||||
import com.vk.dto.music.Artist;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class FFMpeg {
|
public class FFMpeg {
|
||||||
private static final int BUFFER_SIZE = 2048;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
|
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
|
||||||
@@ -25,79 +13,47 @@ public class FFMpeg {
|
|||||||
* @param track Track to be converted
|
* @param track Track to be converted
|
||||||
* @return True if success, else false
|
* @return True if success, else false
|
||||||
*/
|
*/
|
||||||
public static boolean convert(File in, String out, MusicTrack track) {
|
public static void convert(File in, String out, MusicTrack track) throws Throwable {
|
||||||
|
|
||||||
// convert .ts file to .mp3 file via ffmpeg
|
// convert .ts file to .mp3 file via ffmpeg
|
||||||
var sb = new StringBuilder();
|
var cmd = " -y -i \"" + in.getAbsolutePath() + '"'
|
||||||
sb.append("-y -i \"").append(in.getAbsolutePath()).append("\"");
|
+ " -map 0 -dn"
|
||||||
sb.append(" -map 0 -dn ");
|
+ " -loglevel error"
|
||||||
//sb.append(" -loglevel error");
|
+ " -hide_banner"
|
||||||
//sb.append(" -hide_banner");
|
+ " -write_id3v2 1"
|
||||||
sb.append(" -write_id3v2 1");
|
// +" -metadata title=\""+IOUtils.getValidFileName(M3UDownloader.getTitle(track))+'"'
|
||||||
|
// +" -metadata artist=\""+normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", ")))+'"'
|
||||||
String title = null;
|
// +" -metadata album=\""+normalizeMetadata(track.I.getTitle())+'"'
|
||||||
|
+ " -c copy \"" + out + '"';
|
||||||
if (track.f != null) {
|
var ses = FFmpegKit.execute(cmd);
|
||||||
title = track.f;
|
if (ses.getReturnCode().isValueCancel()) throw new RuntimeException(ses.getLogsAsString());
|
||||||
if (!track.g.isEmpty()) {
|
|
||||||
title += " (" + track.g + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title != null) {
|
|
||||||
sb.append(" -metadata title=\"").append(IOUtils.getValidFileName(title)).append("\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track.L != null) {
|
|
||||||
sb.append(" -metadata artist=\"").append(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))).append("\"");
|
|
||||||
} else if (track.C != null) {
|
|
||||||
sb.append(" -metadata artist=\"").append(track.C).append("\"");
|
|
||||||
}
|
|
||||||
if (track.I != null) {
|
|
||||||
sb.append(" -metadata album=\"").append(track.I.getTitle()).append("\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(" -c copy \"").append(out).append("\"");
|
|
||||||
|
|
||||||
var command = sb.toString();
|
|
||||||
|
|
||||||
var session = FFmpegKit.execute(command);
|
|
||||||
var rc = session.getReturnCode();
|
|
||||||
|
|
||||||
if (ReturnCode.isSuccess(rc)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e("FFMpeg", "FFmpegKit failed with return code " + rc);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void copyAssets() {
|
private static String normalizeMetadata(String in) {
|
||||||
try {
|
return in.replace('"', '‟');
|
||||||
String[] files = AndroidUtils.getGlobalContext().getAssets().list("ffmpeg");
|
|
||||||
for (String file : files) {
|
|
||||||
if (file.endsWith(".so")) {
|
|
||||||
InputStream in = AndroidUtils.getGlobalContext().getAssets().open("ffmpeg" + File.separator + file);
|
|
||||||
FileOutputStream out = new FileOutputStream(AndroidUtils.getGlobalContext().getFilesDir() + File.separator + file);
|
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(buffer)) != -1) {
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkFFMpegLibs() {
|
// private static void copyAssets()
|
||||||
var lib = new File(AndroidUtils.getGlobalContext().getFilesDir(), "libffmpegkit_abidetect.so");
|
// throws IOException
|
||||||
if (!lib.exists()) {
|
// {
|
||||||
ToastUtils.a(R.string.ffmpegkit_copy);
|
// try
|
||||||
copyAssets();
|
// {
|
||||||
}
|
// for (var filename:AndroidUtils.getGlobalContext().getAssets().list("ffmpeg"))
|
||||||
}
|
// if(filename.endsWith(".so"))
|
||||||
|
// IOUtils.writeToFile(
|
||||||
|
// new File(AndroidUtils.getGlobalContext().getFilesDir(),filename),
|
||||||
|
// IOUtils.readFully(AndroidUtils.getGlobalContext().getAssets().open("ffmpeg"+File.separator+filename)));
|
||||||
|
// }
|
||||||
|
// catch(IOException e)
|
||||||
|
// { throw e; }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static void checkFFMpegLibs()
|
||||||
|
// {
|
||||||
|
// var lib=new File(AndroidUtils.getGlobalContext().getFilesDir(),"libffmpegkit_abidetect.so");
|
||||||
|
// if(!lib.exists())
|
||||||
|
// {
|
||||||
|
// ToastUtils.a(R.string.ffmpegkit_copy);
|
||||||
|
// copyAssets();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,66 +9,62 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
public class TSMerger {
|
public class TSMerger {
|
||||||
/**
|
/**
|
||||||
* Merge multiple .ts files into one .ts file
|
* Merge multiple .ts files into one .ts file
|
||||||
*
|
*
|
||||||
* @param in Directory with .ts files
|
* @param in Directory with .ts files
|
||||||
* @return True if success, else false
|
|
||||||
*/
|
*/
|
||||||
public static boolean merge(File in, File out) {
|
public static void merge(File in, File out) {
|
||||||
var tsesList = in.listFiles();
|
File[] tsesList = in.listFiles();
|
||||||
|
|
||||||
if (tsesList == null) {
|
if (tsesList == null) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mergedByteArrayStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
sortTsesList(tsesList);
|
sortTsesList(tsesList);
|
||||||
|
|
||||||
for (var file : tsesList) {
|
for (File file : tsesList) {
|
||||||
if (file.getName().endsWith(".ts")) {
|
if (file.getName().endsWith(".ts")) {
|
||||||
try {
|
try {
|
||||||
mergedByteArrayStream.write(IOUtils.readFully(file));
|
mergedByteArrayStream.write(IOUtils.readFully(file));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeOutputFile(out, mergedByteArrayStream);
|
writeOutputFile(out, mergedByteArrayStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sortTsesList(File[] tsesList) {
|
private static void sortTsesList(File[] tsesList) {
|
||||||
Arrays.sort(tsesList, (ts1, ts2) -> {
|
Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
|
||||||
String ts1Name = ts1.getName();
|
|
||||||
String ts2Name = ts2.getName();
|
|
||||||
int i1 = Integer.parseInt(ts1Name.substring(0, ts1Name.indexOf('.', 1)));
|
|
||||||
int i2 = Integer.parseInt(ts2Name.substring(0, ts2Name.indexOf('.', 1)));
|
|
||||||
return Integer.compare(i1, i2);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
|
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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
try {
|
try {
|
||||||
Files.write(out.toPath(), mergedByteArrayStream.toByteArray());
|
Files.write(out.toPath(), bytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException ignored) {
|
||||||
e.printStackTrace();
|
// ignored
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (FileOutputStream fos = new FileOutputStream(out)) {
|
try (FileOutputStream fos = new FileOutputStream(out)) {
|
||||||
fos.write(mergedByteArrayStream.toByteArray());
|
fos.write(bytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException ignored) {
|
||||||
e.printStackTrace();
|
// ignored
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
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 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;
|
||||||
|
|
||||||
|
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(() -> 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 + ')' : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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" : track.C + " - " + 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -70,7 +70,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.Callback callback) {
|
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
@@ -2,7 +2,7 @@ package ru.vtosters.lite.music.downloader;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -10,16 +10,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
public class PlaylistDownloader {
|
public class PlaylistDownloader {
|
||||||
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
||||||
File outDir = new File(path);
|
var outDir = new File(path);
|
||||||
if (!outDir.exists())
|
if (!outDir.exists())
|
||||||
if (outDir.mkdirs())
|
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
|
||||||
Log.v("PlaylistDownloader", "Directory created");
|
else Log.e("PlaylistDownloader", "Directory creation failed");
|
||||||
else
|
var downloadedTracks = new AtomicInteger();
|
||||||
Log.e("PlaylistDownloader", "Directory creation failed");
|
for (var musicTrack : playlist)
|
||||||
|
|
||||||
var downloadedTracks = new AtomicInteger(0);
|
|
||||||
|
|
||||||
for (MusicTrack musicTrack : playlist) {
|
|
||||||
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(int progress) {
|
public void onProgress(int progress) {
|
||||||
@@ -27,8 +23,7 @@ public class PlaylistDownloader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
var currentProgress = downloadedTracks.incrementAndGet();
|
callback.onProgress(downloadedTracks.incrementAndGet());
|
||||||
callback.onProgress(currentProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,13 +35,11 @@ public class PlaylistDownloader {
|
|||||||
public void onSizeReceived(long size, long header) {
|
public void onSizeReceived(long size, long header) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
||||||
var downloadedTracks = new AtomicInteger(0);
|
var downloadedTracks = new AtomicInteger();
|
||||||
|
for (var musicTrack : playlist)
|
||||||
for (MusicTrack musicTrack : playlist) {
|
|
||||||
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(int progress) {
|
public void onProgress(int progress) {
|
||||||
@@ -54,8 +47,7 @@ public class PlaylistDownloader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
var currentProgress = downloadedTracks.incrementAndGet();
|
callback.onProgress(downloadedTracks.incrementAndGet());
|
||||||
callback.onProgress(currentProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -67,6 +59,5 @@ public class PlaylistDownloader {
|
|||||||
public void onSizeReceived(long size, long header) {
|
public void onSizeReceived(long size, long header) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,45 +2,42 @@ package ru.vtosters.lite.music.downloader;
|
|||||||
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
public class ThumbnailDownloader {
|
public class ThumbnailDownloader {
|
||||||
|
|
||||||
public static void downloadThumbnails(MusicTrack track)
|
public static void downloadThumbnails(MusicTrack track) throws IOException {
|
||||||
throws IOException {
|
JSONObject json = track.J();
|
||||||
var obj = track.J();
|
JSONObject album = json.optJSONObject("album");
|
||||||
var trackId = LibVKXClient.asId(track);
|
if (album == null) return;
|
||||||
|
JSONObject thumb = album.optJSONObject("thumb");
|
||||||
if (!obj.has("album")) return;
|
if (thumb == null) return;
|
||||||
var album = obj.optJSONObject("album");
|
JSONArray sizes = thumb.optJSONArray("sizes");
|
||||||
|
String trackId = LibVKXClient.asId(track);
|
||||||
if (!album.has("thumb")) return;
|
if (sizes == null) {
|
||||||
var thumb = album.optJSONObject("thumb");
|
JSONArray names = thumb.names();
|
||||||
|
if (names == null) return;
|
||||||
if (thumb.has("sizes")) {
|
for (int i = 0; i < names.length(); ++i) {
|
||||||
var sizes = thumb.optJSONArray("sizes");
|
String name = names.optString(i);
|
||||||
for (int i = 0; i < sizes.length(); i++) {
|
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
||||||
var size = sizes.optJSONObject(i);
|
|
||||||
var width = size.optInt("width");
|
|
||||||
var src = size.optString("src");
|
|
||||||
downloadThumbnail(src, width, trackId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var names = thumb.names();
|
for (int i = 0; i < sizes.length(); ++i) {
|
||||||
for (int i = 0; i < names.length(); i++) {
|
JSONObject size = sizes.optJSONObject(i);
|
||||||
var name = names.optString(i);
|
int width = size.optInt("width");
|
||||||
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
String src = size.optString("src");
|
||||||
|
downloadThumbnail(src, width, trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadThumbnail(String url, int res, String trackId)
|
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
|
||||||
throws IOException {
|
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
|
||||||
var data = IOUtils.readFully(new URL(url).openStream());
|
|
||||||
IOUtils.writeToFile(FileCacheImplementation.getTrackThumbnail(trackId, res), data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import android.util.Log;
|
|||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.M3UDownloader;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ public class TrackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track, Callback callback) {
|
public static void cacheTrack(MusicTrack track, Callback callback) {
|
||||||
if (CacheDatabaseDelegate.isCached(track.y1())) {
|
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
|
||||||
callback.onSuccess();
|
callback.onSuccess();
|
||||||
return;
|
return;
|
||||||
} else if (track.D.isEmpty()) {
|
} else if (track.D.isEmpty()) {
|
||||||
@@ -33,7 +32,7 @@ public class TrackDownloader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = FileCacheImplementation.getTrackFolder(LibVKXClient.asId(track)).getAbsolutePath();
|
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
|
||||||
download(track, path, callback, M3UDownloader.getInstance(), true);
|
download(track, path, callback, M3UDownloader.getInstance(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onProgress(int progress);
|
void onProgress(int progress);
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.vtosters.lite.music.interfaces;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.Callback;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package ru.vtosters.lite.themes;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.util.Log;
|
|
||||||
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
|
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package ru.vtosters.lite.themes.loaders;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.util.Log;
|
|
||||||
import ru.vtosters.lite.utils.ReflectionUtils;
|
import ru.vtosters.lite.utils.ReflectionUtils;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
@SuppressLint("PrivateApi")
|
||||||
public class AssetManagerHelper {
|
public class AssetManagerHelper {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import ru.vtosters.lite.utils.ReflectionUtils;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ru.vtosters.lite.ui;
|
|||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.InputType;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
@@ -334,6 +335,52 @@ public class PreferenceFragmentUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Preference addEditTextValPreference(
|
||||||
|
PreferenceScreen screen,
|
||||||
|
String key,
|
||||||
|
CharSequence title,
|
||||||
|
CharSequence defval,
|
||||||
|
onPreferenceTextValueChangeListener editTextPrefChangeListener
|
||||||
|
) {
|
||||||
|
return addPreference(screen, key, title, null, null, preference -> {
|
||||||
|
LinearLayout linearLayout = new LinearLayout(screen.getContext());
|
||||||
|
|
||||||
|
final EditText editText = new EditText(screen.getContext());
|
||||||
|
editText.setText(PreferenceManager.getDefaultSharedPreferences(screen.getContext()).getString(key, ""));
|
||||||
|
editText.setHint(title);
|
||||||
|
editText.setTextColor(ThemesUtils.getTextAttr());
|
||||||
|
editText.setHintTextColor(ThemesUtils.getSTextAttr());
|
||||||
|
editText.setText(defval);
|
||||||
|
|
||||||
|
editText.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
|
||||||
|
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
|
editText.setTransformationMethod(null);
|
||||||
|
|
||||||
|
linearLayout.addView(editText);
|
||||||
|
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
|
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
|
||||||
|
margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
|
||||||
|
editText.setLayoutParams(margin);
|
||||||
|
|
||||||
|
new VkAlertDialog.Builder(screen.getContext())
|
||||||
|
.setTitle(title)
|
||||||
|
.setView(linearLayout)
|
||||||
|
.setPositiveButton("OK", (dialog, which) -> {
|
||||||
|
boolean change = editTextPrefChangeListener.onChanged(preference, editText.getText().toString());
|
||||||
|
if (!change)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(screen.getContext())
|
||||||
|
.edit()
|
||||||
|
.putString(key, editText.getText().toString())
|
||||||
|
.apply();
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public interface onPreferenceTextValueChangeListener {
|
public interface onPreferenceTextValueChangeListener {
|
||||||
boolean onChanged(Preference preference, String newValue);
|
boolean onChanged(Preference preference, String newValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package ru.vtosters.lite.ui.dialogs;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
|
||||||
|
|
||||||
public class DisableBattery {
|
|
||||||
public static void alert(Activity activity) {
|
|
||||||
if (!(OEMDetector.isOEM() && Build.VERSION.SDK_INT >= 23 && Preferences.getBoolValue("showDoze", true)))
|
|
||||||
return;
|
|
||||||
String packName = activity.getPackageName();
|
|
||||||
PowerManager manager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
|
|
||||||
if (manager.isIgnoringBatteryOptimizations(packName)) return;
|
|
||||||
new VkAlertDialog.Builder(activity)
|
|
||||||
.setTitle(R.string.batteryissuetitle)
|
|
||||||
.setMessage(R.string.batteryissuesumm)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(R.string.batteryissuebtn1, (dialogInterface, i) -> {
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
var intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
.setData(Uri.parse("package:" + packName));
|
|
||||||
activity.startActivity(intent);
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.batteryissuebtn2,
|
|
||||||
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean("showDoze", false).apply()
|
|
||||||
)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import android.app.Activity;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
import ru.vtosters.lite.downloaders.OTADownloader;
|
import ru.vtosters.lite.downloaders.OTADownloader;
|
||||||
@@ -17,22 +18,25 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
|
|||||||
|
|
||||||
private final OTAUtils mHelper;
|
private final OTAUtils mHelper;
|
||||||
|
|
||||||
public OTADialog(Activity activity) {
|
public OTADialog(Activity activity, boolean isManualCheck) {
|
||||||
super(activity);
|
super(activity);
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
|
|
||||||
mHelper = new OTAUtils(this);
|
mHelper = new OTAUtils(this);
|
||||||
mHelper.loadData();
|
mHelper.loadData(isManualCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkUpdates(Activity activity) {
|
public static void checkUpdates(Activity activity) {
|
||||||
new OTADialog(activity);
|
new OTADialog(activity, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkUpdatesManual(Activity activity) {
|
||||||
|
new OTADialog(activity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpdateApplied() {
|
public void onUpdateApplied(boolean isManualCheck) {
|
||||||
mActivity.runOnUiThread(() -> {
|
mActivity.runOnUiThread(() -> {
|
||||||
// Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show();
|
|
||||||
setTitle(mActivity.getString(R.string.newversion) + " " + mHelper.getNewVersionName())
|
setTitle(mActivity.getString(R.string.newversion) + " " + mHelper.getNewVersionName())
|
||||||
.setView(makeUpdateInfoView(mHelper.getUpdateDescription()))
|
.setView(makeUpdateInfoView(mHelper.getUpdateDescription()))
|
||||||
.setPositiveButton(mActivity.getString(R.string.updateanddownload), () -> {
|
.setPositiveButton(mActivity.getString(R.string.updateanddownload), () -> {
|
||||||
@@ -57,9 +61,18 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpdateCanceled() {
|
public void onUpdateLatest(boolean isManualCheck) {
|
||||||
// mActivity.runOnUiThread(() -> {
|
if (isManualCheck) {
|
||||||
// // Toast.makeText(mActivity, "Обновлений не найдено", Toast.LENGTH_SHORT).show();
|
mActivity.runOnUiThread(() -> {
|
||||||
// });
|
Toast.makeText(mActivity, "У вас установлена последняя версия приложения", Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdateError() {
|
||||||
|
mActivity.runOnUiThread(() -> {
|
||||||
|
Toast.makeText(mActivity, "Ошибка проверки обновления", Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
|
|||||||
FileUtils.l();
|
FileUtils.l();
|
||||||
AutoPlayCacheHolder.d.a();
|
AutoPlayCacheHolder.d.a();
|
||||||
MediaStorage.a();
|
MediaStorage.a();
|
||||||
|
IOUtils.deleteRecursive(AndroidUtils.getGlobalContext().getCacheDir());
|
||||||
updateCacheSize();
|
updateCacheSize();
|
||||||
})
|
})
|
||||||
.setNegativeButton(requireContext().getString(com.vtosters.lite.R.string.cancel), (dialogInterface, i) -> dialogInterface.dismiss())
|
.setNegativeButton(requireContext().getString(com.vtosters.lite.R.string.cancel), (dialogInterface, i) -> dialogInterface.dismiss())
|
||||||
@@ -125,6 +126,7 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
|
|||||||
ImEngineExt.a(ImEngine1.a());
|
ImEngineExt.a(ImEngine1.a());
|
||||||
AutoPlayCacheHolder.d.a();
|
AutoPlayCacheHolder.d.a();
|
||||||
MediaStorage.a();
|
MediaStorage.a();
|
||||||
|
IOUtils.deleteRecursive(AndroidUtils.getGlobalContext().getCacheDir());
|
||||||
}
|
}
|
||||||
case 1 -> {
|
case 1 -> {
|
||||||
SharedPreferences prefs2 = getContext().getSharedPreferences("stickers_storage", Context.MODE_PRIVATE);
|
SharedPreferences prefs2 = getContext().getSharedPreferences("stickers_storage", Context.MODE_PRIVATE);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ru.vtosters.lite.ui.fragments;
|
package ru.vtosters.lite.ui.fragments;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
|
||||||
public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment {
|
public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||||
@@ -8,6 +11,22 @@ public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment
|
|||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
addPreferencesFromResource(R.xml.preferences_gms);
|
addPreferencesFromResource(R.xml.preferences_gms);
|
||||||
|
|
||||||
|
findPreference("microg").setOnPreferenceClickListener((preference) -> {
|
||||||
|
String[] items = {"Уменьшенный MicroG (Vanced MicroG)", "Полноценный MicroG (Замена GMS)"};
|
||||||
|
|
||||||
|
new VkAlertDialog.Builder(requireActivity()).setItems(items, (dialog, which) -> {
|
||||||
|
switch (which) {
|
||||||
|
case 0 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/TeamVanced/VancedMicroG/releases/latest/download/microg.apk")));
|
||||||
|
case 1 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/microg/GmsCore/releases")));
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
|
}).show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ru.vtosters.lite.concurrent.VTExecutors;
|
|||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.downloaders.VideoDownloader;
|
import ru.vtosters.lite.downloaders.VideoDownloader;
|
||||||
import ru.vtosters.lite.music.LastFMScrobbler;
|
import ru.vtosters.lite.music.LastFMScrobbler;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.proxy.ProxyUtils;
|
import ru.vtosters.lite.proxy.ProxyUtils;
|
||||||
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
|
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
@@ -108,9 +108,9 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
findPreference("lastfm_enabled").setEnabled(false);
|
findPreference("lastfm_enabled").setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), CacheDatabaseDelegate.getTrackCount()));
|
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()));
|
||||||
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
|
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
|
||||||
if (CacheDatabaseDelegate.getTrackCount() == 0) {
|
if (MusicCacheImpl.isEmpty()) {
|
||||||
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
|
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
|
||||||
} else {
|
} else {
|
||||||
delcache(requireContext());
|
delcache(requireContext());
|
||||||
@@ -123,7 +123,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
findPreference("invertCachedTracks").setVisible(CacheDatabaseDelegate.hasTracks());
|
findPreference("invertCachedTracks").setVisible(!MusicCacheImpl.isEmpty());
|
||||||
|
|
||||||
if (!LibVKXClient.isVkxInstalled()) {
|
if (!LibVKXClient.isVkxInstalled()) {
|
||||||
findPreference("vkx_sett").setVisible(false);
|
findPreference("vkx_sett").setVisible(false);
|
||||||
@@ -217,7 +217,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.cached_tracks_remove_confirm)
|
.setMessage(R.string.cached_tracks_remove_confirm)
|
||||||
.setPositiveButton(R.string.yes,
|
.setPositiveButton(R.string.yes,
|
||||||
(dialog, which) -> executor.submit(CacheDatabaseDelegate::clear))
|
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
|
||||||
.setNeutralButton(R.string.no,
|
.setNeutralButton(R.string.no,
|
||||||
(dialog, which) -> dialog.cancel())
|
(dialog, which) -> dialog.cancel())
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import android.app.Activity;
|
|||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper;
|
||||||
|
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.activities.LockCreationActivity;
|
||||||
import com.vk.auth.api.VKAccount;
|
import com.vk.auth.api.VKAccount;
|
||||||
import com.vk.core.util.ToastUtils;
|
import com.vk.core.util.ToastUtils;
|
||||||
import com.vk.pushes.PushSubscriber;
|
import com.vk.pushes.PushSubscriber;
|
||||||
@@ -65,7 +70,7 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
|
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
|
||||||
try {
|
try {
|
||||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
intent.setComponent(new ComponentName("com.mgoogle.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);
|
startActivity(intent);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -73,6 +78,22 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findPreference("pin_setup").setOnPreferenceClickListener(preference -> {
|
||||||
|
var intent = new Intent(this.getActivity(), LockCreationActivity.class);
|
||||||
|
startActivityForResult(intent, AppLock.REQUEST_CODE_LOCK_CREATION);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
findPreference("pin_reset").setOnPreferenceClickListener(preference -> {
|
||||||
|
if (AppLock.isEnrolled(requireContext())) {
|
||||||
|
AppLock.getInstance(requireContext()).invalidateEnrollments();
|
||||||
|
AndroidUtils.sendToast(AndroidUtils.getString("pin_reset"));
|
||||||
|
} else {
|
||||||
|
AndroidUtils.sendToast(AndroidUtils.getString("pin_not_set"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
findPreference("tokencopy").setOnPreferenceClickListener(preference -> {
|
findPreference("tokencopy").setOnPreferenceClickListener(preference -> {
|
||||||
copyText(AccountManagerUtils.getUserToken());
|
copyText(AccountManagerUtils.getUserToken());
|
||||||
|
|
||||||
@@ -103,13 +124,6 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
findPreference("datasettings").setOnPreferenceClickListener(preference -> {
|
|
||||||
NavigatorUtils.switchFragment(requireContext(), DataSettingsFragment.class);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("dialogrecomm").setVisible(!Preferences.hasVerification());
|
|
||||||
|
|
||||||
findPreference("updateverifdata").setOnPreferenceClickListener(preference -> {
|
findPreference("updateverifdata").setOnPreferenceClickListener(preference -> {
|
||||||
UsersList.getUsersList();
|
UsersList.getUsersList();
|
||||||
VTVerifications.load(requireContext());
|
VTVerifications.load(requireContext());
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
import ru.vtosters.hooks.GmsHook;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
||||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
||||||
@@ -46,6 +47,11 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
boolean isEMUI = OEMDetector.isEMUI();
|
boolean isEMUI = OEMDetector.isEMUI();
|
||||||
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
|
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
|
||||||
|
|
||||||
|
boolean hasGMS = GmsHook.isGmsInstalled();
|
||||||
|
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 isValidSignature = Preferences.isValidSignature();
|
||||||
boolean isTablet = AndroidUtils.isTablet();
|
boolean isTablet = AndroidUtils.isTablet();
|
||||||
boolean isDebuggable = AndroidUtils.isDebuggable();
|
boolean isDebuggable = AndroidUtils.isDebuggable();
|
||||||
@@ -76,6 +82,12 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Google Market Services package", GMSPackageName, 0, preference -> {
|
||||||
|
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", GMSPackageName));
|
||||||
|
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Valid Signature", "Value: " + isValidSignature, 0, null);
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Valid Signature", "Value: " + isValidSignature, 0, null);
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isTablet", "Value: " + isTablet, 0, null);
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isTablet", "Value: " + isTablet, 0, null);
|
||||||
@@ -90,6 +102,12 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledGoogleGMS", "Value: " + hasGMS, 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 -> {
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Product Name", productName, 0, preference -> {
|
||||||
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
|
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
|
||||||
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
|
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import com.vtosters.lite.R;
|
|||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.themes.ThemesCore;
|
|
||||||
import ru.vtosters.lite.themes.ThemesManager;
|
import ru.vtosters.lite.themes.ThemesManager;
|
||||||
import ru.vtosters.lite.themes.palettes.PalettesManager;
|
import ru.vtosters.lite.themes.palettes.PalettesManager;
|
||||||
import ru.vtosters.lite.ui.components.DockBarEditorManager;
|
import ru.vtosters.lite.ui.components.DockBarEditorManager;
|
||||||
@@ -219,7 +218,7 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setTitle(AndroidUtils.getString("error"))
|
.setTitle(AndroidUtils.getString(R.string.error))
|
||||||
.setMessage(AndroidUtils.getString("error_applying_accent") + ":\n" + e)
|
.setMessage(AndroidUtils.getString("error_applying_accent") + ":\n" + e)
|
||||||
.setPositiveButton("OK", null)
|
.setPositiveButton("OK", null)
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ru.vtosters.lite.ui.fragments;
|
package ru.vtosters.lite.ui.fragments;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -8,11 +9,16 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import com.aefyr.tsg.g2.TelegramStickersService;
|
import com.aefyr.tsg.g2.TelegramStickersService;
|
||||||
import com.vk.about.AboutAppFragment;
|
import com.vk.about.AboutAppFragment;
|
||||||
import com.vk.balance.BalanceFragment;
|
import com.vk.balance.BalanceFragment;
|
||||||
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vk.identity.fragments.IdentityListFragment;
|
import com.vk.identity.fragments.IdentityListFragment;
|
||||||
import com.vk.medianative.MediaImageEncoder;
|
import com.vk.medianative.MediaImageEncoder;
|
||||||
import com.vk.notifications.settings.NotificationsSettingsFragment;
|
import com.vk.notifications.settings.NotificationsSettingsFragment;
|
||||||
@@ -37,6 +43,7 @@ import ru.vtosters.lite.BuildConfig;
|
|||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
||||||
import ru.vtosters.lite.ssfs.Utils;
|
import ru.vtosters.lite.ssfs.Utils;
|
||||||
|
import ru.vtosters.lite.themes.utils.RecolorUtils;
|
||||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
||||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||||
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
||||||
@@ -45,16 +52,14 @@ import ru.vtosters.lite.utils.*;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
||||||
|
public static String ACTION_INVALIDATE_TGS_COUNT = "com.vtosters.lite.intent.action.INVALIDATE_TGS_COUNT";
|
||||||
|
|
||||||
final static public String ACTION_INVALIDATE_TGS_COUNT="com.vtosters.lite.intent.action.INVALIDATE_TGS_COUNT";
|
BroadcastReceiver mTgsReceiver = new BroadcastReceiver() {
|
||||||
|
|
||||||
final BroadcastReceiver mTgsReceiver=new BroadcastReceiver()
|
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent)
|
public void onReceive(Context context, Intent intent) {
|
||||||
{
|
if (ACTION_INVALIDATE_TGS_COUNT.equals(intent.getAction())) {
|
||||||
if(ACTION_INVALIDATE_TGS_COUNT.equals(intent.getAction()))
|
|
||||||
findPreference("tgs_stickers").setSummary(getTGSsumm());
|
findPreference("tgs_stickers").setSummary(getTGSsumm());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,16 +95,21 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
ThemesUtils.setTheme(isDarkTheme ? ThemesUtils.getDarkTheme() : ThemesUtils.getLightTheme(), requireActivity(), true);
|
ThemesUtils.setTheme(isDarkTheme ? ThemesUtils.getDarkTheme() : ThemesUtils.getLightTheme(), requireActivity(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
requireContext().registerReceiver(mTgsReceiver,new IntentFilter(ACTION_INVALIDATE_TGS_COUNT));
|
requireContext().registerReceiver(mTgsReceiver, new IntentFilter(ACTION_INVALIDATE_TGS_COUNT));
|
||||||
|
|
||||||
this.addPreferencesFromResource(R.xml.empty);
|
this.addPreferencesFromResource(R.xml.empty);
|
||||||
|
|
||||||
AndroidUtils.checkLinksVerified(this.requireActivity());
|
PowerManager manager = (PowerManager) requireActivity().getSystemService(Context.POWER_SERVICE);
|
||||||
|
boolean isLinksUnverified = AndroidUtils.isLinksUnverified(requireActivity());
|
||||||
|
boolean isDozingAvailable = Build.VERSION.SDK_INT >= 23 && !manager.isIgnoringBatteryOptimizations(AndroidUtils.getPackageName());
|
||||||
|
boolean areNotificationsDisabled = !AndroidUtils.areNotificationsEnabled();
|
||||||
|
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled();
|
||||||
|
|
||||||
if (AccountManagerUtils.isLogin()) {
|
if (AccountManagerUtils.isLogin()) {
|
||||||
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
|
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
|
||||||
@@ -112,6 +122,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
try {
|
try {
|
||||||
VKAuth.a("logout", false);
|
VKAuth.a("logout", false);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(requireContext(), MainActivity.class)
|
Intent intent = new Intent(requireContext(), MainActivity.class)
|
||||||
@@ -159,8 +170,8 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
AndroidUtils.sendToast(AndroidUtils.getString("systemtheme_enabled"));
|
AndroidUtils.sendToast(AndroidUtils.getString("systemtheme_enabled"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final MaterialSwitchPreference switchPreference = (MaterialSwitchPreference) preference;
|
MaterialSwitchPreference switchPreference = (MaterialSwitchPreference) preference;
|
||||||
final boolean isDarkTheme = !switchPreference.isChecked();
|
boolean isDarkTheme = !switchPreference.isChecked();
|
||||||
switchTheme(isDarkTheme);
|
switchTheme(isDarkTheme);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -175,27 +186,89 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
R.drawable.ic_palette_outline_28,
|
R.drawable.ic_palette_outline_28,
|
||||||
Preferences.systemtheme(),
|
Preferences.systemtheme(),
|
||||||
(preference, o) -> {
|
(preference, o) -> {
|
||||||
Preferences.getPreferences().edit().putBoolean("system_theme", (boolean) o).commit();
|
Preferences.getPreferences().edit().putBoolean("system_theme", (boolean) o).apply();
|
||||||
SystemThemeChangerHook.onThemeChanged(requireActivity().getResources().getConfiguration());
|
SystemThemeChangerHook.onThemeChanged(requireActivity().getResources().getConfiguration());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GmsHook.isAnyServicesInstalled()) {
|
if ((isLinksUnverified || isDozingAvailable || areNotificationsDisabled || isGMSNotInstalled) && !Preferences.getBoolValue("dialogrecomm", false)) {
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.gmsname);
|
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Рекомендации");
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
if (isGMSNotInstalled) {
|
||||||
getPreferenceScreen(),
|
PreferenceFragmentUtils.addPreference(
|
||||||
"",
|
getPreferenceScreen(),
|
||||||
requireContext().getString(R.string.installgms),
|
"",
|
||||||
"",
|
requireContext().getString(R.string.installgms),
|
||||||
R.drawable.ic_about_outline_28,
|
"Отсутствие этих сервисов приводит к поломке фоновых уведомлений и проблемам работы компонентов приложения",
|
||||||
preference -> {
|
RecolorUtils.recolorDrawable(R.drawable.ic_logo_google_28, ThemesUtils.getColor(R.color.red)),
|
||||||
NavigatorUtils.switchFragment(requireContext(), InstallGMSFragment.class);
|
preference -> {
|
||||||
return false;
|
NavigatorUtils.switchFragment(requireContext(), InstallGMSFragment.class);
|
||||||
}
|
return false;
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLinksUnverified) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Не выбраны ссылки для открытия приложением",
|
||||||
|
"Это помешает открытию внешних ссылок для их открытия с помощью этого приложения\n\nВ некоторых случаях необходимо отключить открытие ссылок официальным приложениям ВКонтакте",
|
||||||
|
RecolorUtils.recolorDrawable(R.drawable.ic_linked_outline_28, ThemesUtils.getColor(R.color.red)),
|
||||||
|
preference -> {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:" + AndroidUtils.getPackageName()));
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
} catch (Throwable t1) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + AndroidUtils.getPackageName()));
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDozingAvailable) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Отключите экономию батареи",
|
||||||
|
"Экономия батареи мешает получению фоновых сообщений, уведомлений и работе музыки" + (OEMDetector.isMIUI() ? "\n\nДля устройств Xiaomi необходимо вручную включить в настройках системы пункт Автозапуск данному приложению" : ""),
|
||||||
|
RecolorUtils.recolorDrawable(R.drawable.ic_filter_outline_28, ThemesUtils.getColor(R.color.red)),
|
||||||
|
preference -> {
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
var intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.setData(Uri.parse("package:" + AndroidUtils.getPackageName()));
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areNotificationsDisabled) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Включите уведомления",
|
||||||
|
"Без включенных уведомлений не получится получать сообщения и многое другое",
|
||||||
|
RecolorUtils.recolorDrawable(R.drawable.ic_notifications_outline_28, ThemesUtils.getColor(R.color.red)),
|
||||||
|
preference -> {
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
var intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(Settings.EXTRA_APP_PACKAGE, AndroidUtils.getPackageName());
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.devmenu()) {
|
if (Preferences.devmenu()) {
|
||||||
@@ -440,7 +513,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
"",
|
"",
|
||||||
requireContext().getString(R.string.vtlinterface),
|
requireContext().getString(R.string.vtlinterface),
|
||||||
getValAsString(R.string.showstories, Preferences.stories()),
|
getValAsString(R.string.show_superapps_title, Preferences.superapp()),
|
||||||
R.drawable.ic_interface_outline_28,
|
R.drawable.ic_interface_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
NavigatorUtils.switchFragment(requireContext(), InterfaceFragment.class);
|
NavigatorUtils.switchFragment(requireContext(), InterfaceFragment.class);
|
||||||
@@ -448,6 +521,18 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"currcache",
|
||||||
|
AndroidUtils.getString(R.string.datasettings_title),
|
||||||
|
getString(com.vtosters.lite.R.string.cache_size_summ) + ": " + CacheUtils.humanReadableByteCountBin(IOUtils.getDirSize(AndroidUtils.getGlobalContext().getCacheDir())),
|
||||||
|
R.drawable.ic_document_outline_28,
|
||||||
|
preference -> {
|
||||||
|
NavigatorUtils.switchFragment(requireContext(), DataSettingsFragment.class);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
PreferenceFragmentUtils.addPreference(
|
||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
"",
|
"",
|
||||||
@@ -480,14 +565,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
AndroidUtils.getString("currentLanguage") + " " + AndroidUtils.upString(Locale.getDefault().getDisplayLanguage()),
|
AndroidUtils.getString("currentLanguage") + " " + AndroidUtils.upString(Locale.getDefault().getDisplayLanguage()),
|
||||||
R.drawable.ic_globe_outline_28,
|
R.drawable.ic_globe_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
try {
|
startActivity(new Intent("android.settings.APP_LOCALE_SETTINGS", Uri.parse("package:" + AndroidUtils.getPackageName())));
|
||||||
Intent intent = new Intent("android.settings.APP_LOCALE_SETTINGS", Uri.parse("package:" + AndroidUtils.getPackageName()));
|
|
||||||
startActivity(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
AndroidUtils.sendToast("Ваша система не поддерживает смену языка приложения");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -519,18 +597,71 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (AndroidUtils.isAdbOrDeveloperOptionsEnabled(requireContext().getContentResolver())) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
requireContext().getString(R.string.opencommit),
|
||||||
|
"",
|
||||||
|
R.drawable.ic_link_outline_28,
|
||||||
|
preference -> {
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AboutHook.getCommitLink())));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
PreferenceFragmentUtils.addPreference(
|
||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
"",
|
"",
|
||||||
requireContext().getString(R.string.opencommit),
|
"Наши чаты",
|
||||||
"",
|
"Активные чаты по обсуждению модификации и многому другому",
|
||||||
R.drawable.ic_link_outline_28,
|
R.drawable.ic_message_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AboutHook.getCommitLink())));
|
var args = new Bundle();
|
||||||
|
var dialog = new AboutInformation();
|
||||||
|
|
||||||
|
args.putString("vk_link", "https://vk.me/join/jKE1bIZczHTF2mLkuoiQcd0qIBYgHuGFPRA=");
|
||||||
|
args.putString("tg_link", "https://t.me/vtosterschat");
|
||||||
|
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(requireFragmentManager(), "");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Наши сообщества",
|
||||||
|
"Самая свежая информация об обновлениях и не только!",
|
||||||
|
R.drawable.users_3_outline_28,
|
||||||
|
preference -> {
|
||||||
|
var args = new Bundle();
|
||||||
|
var dialog = new AboutInformation();
|
||||||
|
|
||||||
|
args.putString("vk_link", "https://vk.com/vtosters_official");
|
||||||
|
args.putString("tg_link", "https://t.me/vtosters");
|
||||||
|
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(requireFragmentManager(), "");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Preferences.hasVerification() && !Preferences.getBoolValue("dialogrecomm", false)) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Помочь проекту",
|
||||||
|
"За донат можно получить приятные бонусы на аккаунт",
|
||||||
|
R.drawable.ic_money_circle_outline_28,
|
||||||
|
preference -> {
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://vtosters.app/donate")));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
PreferenceFragmentUtils.addPreference(
|
||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
@@ -566,7 +697,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
"",
|
"",
|
||||||
R.drawable.ic_download_outline_28,
|
R.drawable.ic_download_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
OTADialog.checkUpdates(getActivity());
|
OTADialog.checkUpdatesManual(getActivity());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -574,14 +705,44 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy()
|
public void onDestroy() {
|
||||||
{
|
|
||||||
requireContext().unregisterReceiver(mTgsReceiver);
|
requireContext().unregisterReceiver(mTgsReceiver);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
updateCacheSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCacheSize() {
|
||||||
|
findPreference("currcache").setSummary(getString(com.vtosters.lite.R.string.cache_size_summ) + ": " + CacheUtils.humanReadableByteCountBin(IOUtils.getDirSize(AndroidUtils.getGlobalContext().getCacheDir())));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int T4() {
|
public int T4() {
|
||||||
return R.string.notification_settings;
|
return R.string.notification_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AboutInformation extends DialogFragment {
|
||||||
|
@Override
|
||||||
|
public AlertDialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(requireActivity());
|
||||||
|
|
||||||
|
String[] items = {AndroidUtils.getString(R.string.app_name_alter), "Telegram"};
|
||||||
|
|
||||||
|
builder.setItems(items, (dialog, which) -> {
|
||||||
|
switch (which) {
|
||||||
|
case 0 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getArguments().getString("vk_link"))));
|
||||||
|
case 1 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getArguments().getString("tg_link"))));
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,44 +41,38 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
TelegramStickersGrabber mGrabber;
|
TelegramStickersGrabber mGrabber;
|
||||||
StickerPackAdapter mAdapter;
|
StickerPackAdapter mAdapter;
|
||||||
boolean movePending = false;
|
boolean movePending = false;
|
||||||
int from,to;
|
int from, to;
|
||||||
final BroadcastReceiver mReceiver=new BroadcastReceiver() {
|
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (!intent.getAction().equals(ACTION_RELOAD)||mAdapter==null)return;
|
if (!intent.getAction().equals(ACTION_RELOAD) || mAdapter == null) return;
|
||||||
try
|
try {
|
||||||
{
|
if (movePending) {
|
||||||
if(movePending)
|
movePending = false;
|
||||||
{
|
|
||||||
movePending=false;
|
|
||||||
mAdapter.notifyItemMoved(from, to);
|
mAdapter.notifyItemMoved(from, to);
|
||||||
from=to=-1;
|
from = to = -1;
|
||||||
}
|
} else mAdapter.notifyDataSetChanged();
|
||||||
else mAdapter.notifyDataSetChanged();
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
|
||||||
{ e.printStackTrace(); }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreateMenu(Menu menu)
|
protected void onCreateMenu(Menu menu) {
|
||||||
{
|
|
||||||
menu.add(0, 0, 0, "")
|
menu.add(0, 0, 0, "")
|
||||||
.setIcon(ThemesUtils.recolorDrawable(requireContext().getDrawable(R.drawable.ic_add_outline_28)))
|
.setIcon(ThemesUtils.recolorDrawable(requireContext().getDrawable(R.drawable.ic_add_outline_28)))
|
||||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
menu.add(0,1,0,"")
|
menu.add(0, 1, 0, "")
|
||||||
.setIcon(R.drawable.ic_settings_outline_28)
|
.setIcon(R.drawable.ic_settings_outline_28)
|
||||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
super.onCreateMenu(menu);
|
super.onCreateMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
{
|
switch (item.getItemId()) {
|
||||||
switch (item.getItemId())
|
|
||||||
{
|
|
||||||
case 0 -> addTgsPack();
|
case 0 -> addTgsPack();
|
||||||
case 1 -> openMenu(null);
|
case 1 -> openMenu(null);
|
||||||
}
|
}
|
||||||
@@ -86,7 +80,7 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openMenu(String toast) {
|
private void openMenu(String toast) {
|
||||||
if(toast!=null)Toast.makeText(requireContext(),toast,Toast.LENGTH_SHORT).show();
|
if (toast != null) Toast.makeText(requireContext(), toast, Toast.LENGTH_SHORT).show();
|
||||||
NavigatorUtils.switchFragment(requireContext(), StickersPreferencesFragment.class);
|
NavigatorUtils.switchFragment(requireContext(), StickersPreferencesFragment.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,25 +89,25 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
requireContext().registerReceiver(mReceiver, new IntentFilter(ACTION_RELOAD));
|
requireContext().registerReceiver(mReceiver, new IntentFilter(ACTION_RELOAD));
|
||||||
|
|
||||||
mGrabber=new TelegramStickersGrabber(TGPref.getTGBotKey());
|
mGrabber = new TelegramStickersGrabber(TGPref.getTGBotKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
|
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
|
||||||
setTitle(R.string.vtltgs);
|
setTitle(R.string.vtltgs);
|
||||||
|
|
||||||
final var container=new FrameLayout(requireContext());
|
final var container = new FrameLayout(requireContext());
|
||||||
final var recycler=new RecyclerView(requireContext());
|
final var recycler = new RecyclerView(requireContext());
|
||||||
|
|
||||||
mAdapter=new StickerPackAdapter();
|
mAdapter = new StickerPackAdapter();
|
||||||
recycler.setAdapter(mAdapter);
|
recycler.setAdapter(mAdapter);
|
||||||
|
|
||||||
final var callback=new StickerTouchHelperCallback(mAdapter);
|
final var callback = new StickerTouchHelperCallback(mAdapter);
|
||||||
final var touchHelper=new ItemTouchHelper(callback);
|
final var touchHelper = new ItemTouchHelper(callback);
|
||||||
touchHelper.attachToRecyclerView(recycler);
|
touchHelper.attachToRecyclerView(recycler);
|
||||||
|
|
||||||
recycler.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));
|
recycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
|
||||||
container.addView(recycler,new ViewGroup.LayoutParams(-1,-1));
|
container.addView(recycler, new ViewGroup.LayoutParams(-1, -1));
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -129,79 +123,69 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
requireContext().unregisterReceiver(mReceiver);
|
requireContext().unregisterReceiver(mReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTgsPack()
|
private void addTgsPack() {
|
||||||
{
|
final var method = TGPref.getTGConnectMethod();
|
||||||
final var method=TGPref.getTGConnectMethod();
|
if (method != -1) {
|
||||||
if(method!=-1)
|
|
||||||
{
|
|
||||||
initGrabber();
|
initGrabber();
|
||||||
switch(method)
|
switch (method) {
|
||||||
{
|
case TYPE_SOCKS -> {
|
||||||
case TYPE_SOCKS ->
|
if (TGPref.getTGProxyIP() == null) openMenu(requireContext().getString(R.string.stickersproxy1));
|
||||||
{
|
|
||||||
if (TGPref.getTGProxyIP()==null)openMenu(requireContext().getString(R.string.stickersproxy1));
|
|
||||||
}
|
}
|
||||||
case TYPE_DIRECT ->
|
case TYPE_DIRECT -> {
|
||||||
{
|
|
||||||
initGrabber();
|
initGrabber();
|
||||||
final Runnable lambda = () ->
|
final Runnable lambda = () ->
|
||||||
{
|
{
|
||||||
final var container=new LinearLayout(requireContext());
|
final var container = new LinearLayout(requireContext());
|
||||||
|
|
||||||
final var input=new EditText(requireContext());
|
final var input = new EditText(requireContext());
|
||||||
final var inputLP = new ViewGroup.MarginLayoutParams(-1,-2);
|
final var inputLP = new ViewGroup.MarginLayoutParams(-1, -2);
|
||||||
inputLP.setMargins(
|
inputLP.setMargins(
|
||||||
AndroidUtils.dp2px(24.f),
|
AndroidUtils.dp2px(24.f),
|
||||||
0,
|
0,
|
||||||
AndroidUtils.dp2px(24.f),
|
AndroidUtils.dp2px(24.f),
|
||||||
0);
|
0);
|
||||||
container.addView(input,inputLP);
|
container.addView(input, inputLP);
|
||||||
|
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.stickershelp1)
|
.setTitle(R.string.stickershelp1)
|
||||||
// .setMessage(R.string.stickershelp2)
|
// .setMessage(R.string.stickershelp2)
|
||||||
.setView(container)
|
.setView(container)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||||
{
|
{
|
||||||
var packId=input.getText().toString();
|
var packId = input.getText().toString();
|
||||||
if (packId.startsWith("https://"))
|
if (packId.startsWith("https://"))
|
||||||
packId=packId.substring(packId.lastIndexOf('/')+1);
|
packId = packId.substring(packId.lastIndexOf('/') + 1);
|
||||||
TelegramStickersService.getInstance(requireContext())
|
TelegramStickersService.getInstance(requireContext())
|
||||||
.requestPackDownload(
|
.requestPackDownload(
|
||||||
packId,
|
packId,
|
||||||
new File(requireContext().getFilesDir(),"VT-Stickers/"+packId));
|
new File(requireContext().getFilesDir(), "VT-Stickers/" + packId));
|
||||||
})
|
})
|
||||||
.setNeutralButton(android.R.string.cancel,(dialog, which) -> dialog.dismiss())
|
.setNeutralButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||||
.show();
|
.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
if(TGPref.getTGBotKey()==null)enterBotKey(() -> checkApiKey(lambda));
|
if (TGPref.getTGBotKey() == null) enterBotKey(() -> checkApiKey(lambda));
|
||||||
else checkApiKey(lambda);
|
else checkApiKey(lambda);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else openMenu(requireContext().getString(R.string.stickersconnection));
|
||||||
else openMenu(requireContext().getString(R.string.stickersconnection));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initGrabber() {
|
private void initGrabber() {
|
||||||
switch(TGPref.getTGConnectMethod())
|
switch (TGPref.getTGConnectMethod()) {
|
||||||
{
|
case TYPE_DIRECT -> {
|
||||||
case TYPE_DIRECT ->
|
TelegramStickersGrabber.USE_PROXY = false;
|
||||||
{
|
|
||||||
TelegramStickersGrabber.USE_PROXY=false;
|
|
||||||
mGrabber.resetProxy();
|
mGrabber.resetProxy();
|
||||||
}
|
}
|
||||||
case TYPE_SOCKS ->
|
case TYPE_SOCKS -> {
|
||||||
{
|
if (TGPref.getTGProxyIP() == null) break;
|
||||||
if(TGPref.getTGProxyIP()==null)break;
|
TelegramStickersGrabber.USE_PROXY = true;
|
||||||
TelegramStickersGrabber.USE_PROXY=true;
|
TelegramStickersGrabber.PROXY_IP = TGPref.getTGProxyIP();
|
||||||
TelegramStickersGrabber.PROXY_IP=TGPref.getTGProxyIP();
|
TelegramStickersGrabber.PROXY_PORT = TGPref.getTGProxyPort();
|
||||||
TelegramStickersGrabber.PROXY_PORT=TGPref.getTGProxyPort();
|
if (TGPref.isTGProxyPassEnabled()) {
|
||||||
if (TGPref.isTGProxyPassEnabled())
|
TelegramStickersGrabber.HAS_PASS = true;
|
||||||
{
|
TelegramStickersGrabber.PROXY_USER = TGPref.getTGProxyUser();
|
||||||
TelegramStickersGrabber.HAS_PASS=true;
|
TelegramStickersGrabber.PROXY_PASS = TGPref.getTGProxyPass();
|
||||||
TelegramStickersGrabber.PROXY_USER=TGPref.getTGProxyUser();
|
|
||||||
TelegramStickersGrabber.PROXY_PASS=TGPref.getTGProxyPass();
|
|
||||||
}
|
}
|
||||||
mGrabber.enableProxy();
|
mGrabber.enableProxy();
|
||||||
}
|
}
|
||||||
@@ -209,42 +193,41 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
TelegramStickersGrabber.updateURLs();
|
TelegramStickersGrabber.updateURLs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enterBotKey(Runnable callback)
|
private void enterBotKey(Runnable callback) {
|
||||||
{
|
final var container = new LinearLayout(requireContext());
|
||||||
final var container=new LinearLayout(requireContext());
|
|
||||||
|
|
||||||
final var input=new EditText(requireContext());
|
final var input = new EditText(requireContext());
|
||||||
final var inputLP=new ViewGroup.MarginLayoutParams(-1,-2);
|
final var inputLP = new ViewGroup.MarginLayoutParams(-1, -2);
|
||||||
inputLP.setMargins(
|
inputLP.setMargins(
|
||||||
AndroidUtils.dp2px(24.f),
|
AndroidUtils.dp2px(24.f),
|
||||||
0,
|
0,
|
||||||
AndroidUtils.dp2px(24.f),
|
AndroidUtils.dp2px(24.f),
|
||||||
0);
|
0);
|
||||||
container.addView(input,inputLP);
|
container.addView(input, inputLP);
|
||||||
|
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setTitle(requireContext().getString(R.string.stickersapi5))
|
.setTitle(requireContext().getString(R.string.stickersapi5))
|
||||||
.setMessage(requireContext().getString(R.string.stickersapi6))
|
.setMessage(requireContext().getString(R.string.stickersapi6))
|
||||||
.setView(container)
|
.setView(container)
|
||||||
.setPositiveButton(android.R.string.ok,(dialog, which) ->
|
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||||
{
|
{
|
||||||
TGPref.setTGBotKey(input.getText().toString());
|
TGPref.setTGBotKey(input.getText().toString());
|
||||||
if(callback!=null)callback.run();
|
if (callback != null) callback.run();
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel,(dialog, which) -> dialog.dismiss())
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||||
.setNeutralButton(requireContext().getString(R.string.stickersapi7),
|
.setNeutralButton(requireContext().getString(R.string.stickersapi7),
|
||||||
(dialog, which) ->
|
(dialog, which) ->
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setTitle(requireContext().getString(R.string.stickersapi8))
|
.setTitle(requireContext().getString(R.string.stickersapi8))
|
||||||
.setMessage(requireContext().getString(R.string.stickersapi9) +
|
.setMessage(requireContext().getString(R.string.stickersapi9) +
|
||||||
requireContext().getString(R.string.stickersapi10))
|
requireContext().getString(R.string.stickersapi10))
|
||||||
.setNeutralButton(AndroidUtils.getString("open_bot"),(dl, i) -> {
|
.setNeutralButton(AndroidUtils.getString("open_bot"), (dl, i) -> {
|
||||||
final var intent=new Intent(Intent.ACTION_VIEW,Uri.parse("https://t.me/botfather"))
|
final var intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/botfather"))
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
requireActivity().startActivity(intent);
|
requireActivity().startActivity(intent);
|
||||||
})
|
})
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
)
|
)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
@@ -252,37 +235,33 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
private void checkApiKey(Runnable callback) {
|
private void checkApiKey(Runnable callback) {
|
||||||
mGrabber.setBotApiKey(TGPref.getTGBotKey());
|
mGrabber.setBotApiKey(TGPref.getTGBotKey());
|
||||||
|
|
||||||
final var dialog=new ProgressDialog(requireContext());
|
final var dialog = new ProgressDialog(requireContext());
|
||||||
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
dialog.setMessage(requireContext().getString(R.string.stickersapi1));
|
dialog.setMessage(requireContext().getString(R.string.stickersapi1));
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
mGrabber.checkKey(new TelegramStickersGrabber.KeyCheckListener()
|
mGrabber.checkKey(new TelegramStickersGrabber.KeyCheckListener() {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void onKeyChecked(boolean ok)
|
public void onKeyChecked(boolean ok) {
|
||||||
{
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
if (!ok)
|
if (!ok) {
|
||||||
{
|
Toast.makeText(requireContext(), requireContext().getString(R.string.stickersapi2), Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(requireContext(), requireContext().getString(R.string.stickersapi2),Toast.LENGTH_SHORT).show();
|
|
||||||
TGPref.setTGBotKey(null);
|
TGPref.setTGBotKey(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TelegramStickersService.getInstance(requireContext())
|
TelegramStickersService.getInstance(requireContext())
|
||||||
.setBotKey(TGPref.getTGBotKey());
|
.setBotKey(TGPref.getTGBotKey());
|
||||||
|
|
||||||
if (callback!=null)callback.run();
|
if (callback != null) callback.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNetError()
|
public void onNetError() {
|
||||||
{
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.stickersapi3))
|
.setMessage(requireContext().getString(R.string.stickersapi3))
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(requireContext().getString(R.string.stickersapi4),(di,which) -> checkApiKey(callback))
|
.setPositiveButton(requireContext().getString(R.string.stickersapi4), (di, which) -> checkApiKey(callback))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package ru.vtosters.lite.utils;
|
package ru.vtosters.lite.utils;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.verify.domain.DomainVerificationManager;
|
import android.content.pm.verify.domain.DomainVerificationManager;
|
||||||
@@ -14,11 +15,10 @@ import android.provider.Settings;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import com.vk.core.util.Screen;
|
import com.vk.core.util.Screen;
|
||||||
import com.vk.core.util.ToastUtils;
|
import com.vk.core.util.ToastUtils;
|
||||||
import com.vtosters.lite.general.fragments.WebViewFragment;
|
import com.vtosters.lite.general.fragments.WebViewFragment;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -30,8 +30,6 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
|
||||||
|
|
||||||
public class AndroidUtils {
|
public class AndroidUtils {
|
||||||
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";
|
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";
|
||||||
|
|
||||||
@@ -39,6 +37,12 @@ public class AndroidUtils {
|
|||||||
return 0 != (AndroidUtils.getGlobalContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE);
|
return 0 != (AndroidUtils.getGlobalContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isAdbOrDeveloperOptionsEnabled(ContentResolver contentResolver) {
|
||||||
|
int adbEnabled = Settings.Global.getInt(contentResolver, Settings.Global.ADB_ENABLED, 0);
|
||||||
|
int developerOptionsEnabled = Settings.Global.getInt(contentResolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
|
||||||
|
return adbEnabled == 1 || developerOptionsEnabled == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isTablet() {
|
public static boolean isTablet() {
|
||||||
return Screen.l(getGlobalContext());
|
return Screen.l(getGlobalContext());
|
||||||
}
|
}
|
||||||
@@ -117,8 +121,8 @@ public class AndroidUtils {
|
|||||||
new WebViewFragment.g(url).l().m().h().j().a(activity);
|
new WebViewFragment.g(url).l().m().h().j().a(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkLinksVerified(Activity activity) {
|
public static boolean isLinksUnverified(Activity activity) {
|
||||||
if (Build.VERSION.SDK_INT < 31) return;
|
if (Build.VERSION.SDK_INT < 31) return false;
|
||||||
|
|
||||||
DomainVerificationManager manager = activity.getSystemService(DomainVerificationManager.class);
|
DomainVerificationManager manager = activity.getSystemService(DomainVerificationManager.class);
|
||||||
DomainVerificationUserState userState;
|
DomainVerificationUserState userState;
|
||||||
@@ -126,7 +130,7 @@ public class AndroidUtils {
|
|||||||
try {
|
try {
|
||||||
userState = manager.getDomainVerificationUserState(getPackageName());
|
userState = manager.getDomainVerificationUserState(getPackageName());
|
||||||
} catch (PackageManager.NameNotFoundException ignore) {
|
} catch (PackageManager.NameNotFoundException ignore) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasUnverified = false;
|
boolean hasUnverified = false;
|
||||||
@@ -138,32 +142,7 @@ public class AndroidUtils {
|
|||||||
hasUnverified = stateValue != null && stateValue != DomainVerificationUserState.DOMAIN_STATE_VERIFIED && stateValue != DomainVerificationUserState.DOMAIN_STATE_SELECTED;
|
hasUnverified = stateValue != null && stateValue != DomainVerificationUserState.DOMAIN_STATE_VERIFIED && stateValue != DomainVerificationUserState.DOMAIN_STATE_SELECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUnverified) {
|
return hasUnverified;
|
||||||
if (getBoolValue("showUnverifDialog", true)) {
|
|
||||||
new VkAlertDialog.Builder(activity)
|
|
||||||
.setTitle(com.vtosters.lite.R.string.warning)
|
|
||||||
.setMessage(AndroidUtils.getString("app_open_by_default_settings"))
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(com.vtosters.lite.R.string.social_graph_skip,
|
|
||||||
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean("showUnverifDialog", false).apply()
|
|
||||||
)
|
|
||||||
.setNeutralButton(com.vtosters.lite.R.string.open_settings,
|
|
||||||
(dialogInterface, i) -> {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:" + getPackageName()));
|
|
||||||
activity.startActivity(intent);
|
|
||||||
} catch (Throwable t1) {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
|
|
||||||
activity.startActivity(intent);
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String MD5(String s) {
|
public static String MD5(String s) {
|
||||||
@@ -185,6 +164,15 @@ public class AndroidUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean areNotificationsEnabled() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationManager manager = (NotificationManager) getGlobalContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
return manager.areNotificationsEnabled();
|
||||||
|
} else {
|
||||||
|
return NotificationManagerCompat.from(getGlobalContext()).areNotificationsEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String getRandomString(int sizeOfRandomString) {
|
private static String getRandomString(int sizeOfRandomString) {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
StringBuilder sb = new StringBuilder(sizeOfRandomString);
|
StringBuilder sb = new StringBuilder(sizeOfRandomString);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class ExternalLinkParser {
|
|||||||
|
|
||||||
parseVKVideo(videoFile, qualities, urls);
|
parseVKVideo(videoFile, qualities, urls);
|
||||||
|
|
||||||
if (urls.size() > 0)
|
if (!urls.isEmpty())
|
||||||
if (getBoolValue("maxquality", false) || qualities.size() == 1)
|
if (getBoolValue("maxquality", false) || qualities.size() == 1)
|
||||||
startExternalVideo(context, urls.get(0));
|
startExternalVideo(context, urls.get(0));
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,40 +1,13 @@
|
|||||||
package ru.vtosters.lite.utils;
|
package ru.vtosters.lite.utils;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherInputStream;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class IOUtils {
|
public class IOUtils {
|
||||||
public static int BUFFER_SIZE = 8192;
|
public static int BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
public static byte[] decodeStream(InputStream encIs, byte[] aesKey)
|
|
||||||
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException {
|
|
||||||
CipherInputStream cip = new CipherInputStream(encIs, getCipher(aesKey));
|
|
||||||
return readFully(cip);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Cipher getCipher(byte[] aesKey)
|
|
||||||
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
Key keySpec = new SecretKeySpec(aesKey, "AES");
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
Arrays.fill(iv, (byte) 0x0);
|
|
||||||
IvParameterSpec ivps = new IvParameterSpec(iv);
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
|
|
||||||
return cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream openStream(String url)
|
public static InputStream openStream(String url)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
URLConnection connection = new URL(url).openConnection();
|
URLConnection connection = new URL(url).openConnection();
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import com.guardanis.applock.AppLock;
|
||||||
|
import com.guardanis.applock.activities.UnlockActivity;
|
||||||
|
import com.vk.core.fragments.FragmentImpl;
|
||||||
|
import com.vk.navigation.Navigator;
|
||||||
import com.vtosters.lite.MainActivity;
|
import com.vtosters.lite.MainActivity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -42,6 +46,11 @@ public class LifecycleUtils {
|
|||||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||||
public void onActivityCreated(Activity activity, Bundle bundle) {
|
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||||
activities.add(activity);
|
activities.add(activity);
|
||||||
|
|
||||||
|
if (AppLock.isUnlockRequired(activity) && activity.getClass() != MainActivity.class) {
|
||||||
|
var intent = new Intent(activity, UnlockActivity.class);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||||
@@ -52,7 +61,7 @@ public class LifecycleUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Activity getCurrentActivity() {
|
public static Activity getCurrentActivity() {
|
||||||
return activities.size() > 0 ? activities.get(activities.size() - 1) : new MainActivity();
|
return !activities.isEmpty() ? activities.get(activities.size() - 1) : new MainActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void restartApplication() {
|
public static void restartApplication() {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class OTAUtils {
|
|||||||
this.mListener = listener;
|
this.mListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadData() {
|
public void loadData(boolean isManualCheck) {
|
||||||
Request release = new Request.a()
|
Request release = new Request.a()
|
||||||
.b(LATEST_RELEASE_URL)
|
.b(LATEST_RELEASE_URL)
|
||||||
.a();
|
.a();
|
||||||
@@ -31,13 +31,13 @@ public class OTAUtils {
|
|||||||
@Override
|
@Override
|
||||||
public void a(Call call, IOException e) {
|
public void a(Call call, IOException e) {
|
||||||
Log.d("OTAHelper", "Error while getting latest release info: " + e);
|
Log.d("OTAHelper", "Error while getting latest release info: " + e);
|
||||||
mListener.onUpdateCanceled();
|
mListener.onUpdateError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void a(Call call, Response response) throws IOException {
|
public void a(Call call, Response response) throws IOException {
|
||||||
try {
|
try {
|
||||||
setData(response);
|
setData(response, isManualCheck);
|
||||||
} catch (JSONException | NullPointerException e) {
|
} catch (JSONException | NullPointerException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ public class OTAUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void setData(Response response) throws IOException, JSONException {
|
void setData(Response response, boolean isManualCheck) throws IOException, JSONException {
|
||||||
mReleaseJson = new JSONObject(response.a().g());
|
mReleaseJson = new JSONObject(response.a().g());
|
||||||
|
|
||||||
String tag = mReleaseJson.getString("tag_name");
|
String tag = mReleaseJson.getString("tag_name");
|
||||||
@@ -58,7 +58,7 @@ public class OTAUtils {
|
|||||||
@Override
|
@Override
|
||||||
public void a(Call call, IOException e) {
|
public void a(Call call, IOException e) {
|
||||||
Log.d("OTAHelper", "Error while getting latest commit info: " + e);
|
Log.d("OTAHelper", "Error while getting latest commit info: " + e);
|
||||||
mListener.onUpdateCanceled();
|
mListener.onUpdateError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -67,12 +67,12 @@ public class OTAUtils {
|
|||||||
mCommitJson = new JSONObject(response.a().g());
|
mCommitJson = new JSONObject(response.a().g());
|
||||||
mCommitSHA = mCommitJson.getJSONObject("object").getString("sha");
|
mCommitSHA = mCommitJson.getJSONObject("object").getString("sha");
|
||||||
if (isNewVersion())
|
if (isNewVersion())
|
||||||
mListener.onUpdateApplied();
|
mListener.onUpdateApplied(isManualCheck);
|
||||||
else
|
else
|
||||||
mListener.onUpdateCanceled();
|
mListener.onUpdateLatest(isManualCheck);
|
||||||
} catch (NullPointerException | JSONException e) {
|
} catch (NullPointerException | JSONException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
mListener.onUpdateCanceled();
|
mListener.onUpdateError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -118,9 +118,10 @@ public class OTAUtils {
|
|||||||
|
|
||||||
public interface OTAListener {
|
public interface OTAListener {
|
||||||
|
|
||||||
void onUpdateApplied();
|
void onUpdateApplied(boolean isManualCheck);
|
||||||
|
|
||||||
void onUpdateCanceled();
|
void onUpdateError();
|
||||||
|
|
||||||
|
void onUpdateLatest(boolean isManualCheck);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ public class SuperAppUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static JSONObject superapp(JSONObject json) throws JSONException {
|
public static JSONObject superapp(JSONObject json) throws JSONException {
|
||||||
var superApps = Preferences.getPreferences().getString("superapp_items",
|
var superApps = Preferences.getPreferences().getString("superapp_items", "menu,miniapps,vkpay_slim,greeting,promo,holiday,weather,sport,games,informer,food,event,music,vk_run").split(",");
|
||||||
"menu,miniapps,vkpay_slim,greeting,promo,holiday,weather,sport,games,informer,food,event,music,vk_run").split(",");
|
|
||||||
if (superApps.length == 0) return json;
|
if (superApps.length == 0) return json;
|
||||||
|
|
||||||
var oldItems = json.optJSONArray("items");
|
var oldItems = json.optJSONArray("items");
|
||||||
@@ -31,7 +31,24 @@ public class SuperAppUtils {
|
|||||||
var type = item.optString("type");
|
var type = item.optString("type");
|
||||||
for (String superApp : superApps) {
|
for (String superApp : superApps) {
|
||||||
if (type.equals(superApp))
|
if (type.equals(superApp))
|
||||||
newItems.put(item);
|
if (superApp.equals("menu")) { // superapp menu fix
|
||||||
|
String menuitems = "groups,audios,stickers,podcasts,shopping,lives,videos,games,vk_calls,vk_pay,ads_easy_promote,classifieds,events";
|
||||||
|
JSONObject menuItem = new JSONObject();
|
||||||
|
JSONArray menuItems = new JSONArray();
|
||||||
|
|
||||||
|
generateMenuItems(menuitems, menuItems);
|
||||||
|
|
||||||
|
JSONObject object = new JSONObject();
|
||||||
|
object.put("count", menuitems.split(",").length);
|
||||||
|
object.put("items", menuItems);
|
||||||
|
|
||||||
|
menuItem.put("type", "menu");
|
||||||
|
menuItem.put("object", object);
|
||||||
|
|
||||||
|
newItems.put(menuItem);
|
||||||
|
} else {
|
||||||
|
newItems.put(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < superApps.length; i++) {
|
for (int i = 0; i < superApps.length; i++) {
|
||||||
@@ -46,4 +63,16 @@ public class SuperAppUtils {
|
|||||||
|
|
||||||
return json.putOpt("items", newItems);
|
return json.putOpt("items", newItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void generateMenuItems(String str, JSONArray jsonArray) throws JSONException {
|
||||||
|
String[] strings = str.split(",");
|
||||||
|
|
||||||
|
for (String string : strings) {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
|
||||||
|
jsonObject.put("name", string);
|
||||||
|
|
||||||
|
jsonArray.put(jsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.database.sqlite.SQLiteDatabase;
|
|||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.vk.usersstore.contentprovider.a.UsersDbHelper;
|
import com.vk.usersstore.contentprovider.a.UsersDbHelper;
|
||||||
import com.vtosters.lite.auth.VKAuth;
|
import com.vtosters.lite.auth.VKAuth;
|
||||||
@@ -76,7 +75,7 @@ public class VKAccountDB {
|
|||||||
var dir = new File(getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS), "/VTLBackups/");
|
var dir = new File(getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS), "/VTLBackups/");
|
||||||
var file = new File(dir, "saved_accounts_" + dateFormat.format(new Date()) + ".vtl");
|
var file = new File(dir, "saved_accounts_" + dateFormat.format(new Date()) + ".vtl");
|
||||||
|
|
||||||
try (InputStream inputStream = new FileInputStream(db.getPath())){
|
try (InputStream inputStream = new FileInputStream(db.getPath())) {
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(file);
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package ru.vtosters.lite.utils.music;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MusicCacheStorageUtils {
|
||||||
|
public static File getCacheStorageDir() {
|
||||||
|
return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
public static File getTrackDirById(final String trackId) {
|
||||||
|
final var res = new File(getCacheStorageDir(), trackId);
|
||||||
|
res.mkdirs();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getTrackFile(final String trackId) {
|
||||||
|
return new File(getTrackDirById(trackId), "track.mp3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
public static File getThumbDirById(final String trackId) {
|
||||||
|
final var res = new File(getTrackDirById(trackId), "thumbs");
|
||||||
|
res.mkdirs();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getTrackThumb(final String trackId, final int factor) {
|
||||||
|
return new File(getThumbDirById(trackId), "thumb_" + factor + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeTrackDirById(final String trackId) {
|
||||||
|
IOUtils.deleteRecursive(getTrackDirById(trackId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
IOUtils.deleteRecursive(getCacheStorageDir());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package ru.vtosters.lite.utils.music;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
|
|
||||||
|
public class VKXUtils {
|
||||||
|
public static boolean isVkxCached(int playlist_id, int owner_id) {
|
||||||
|
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
||||||
|
@Override
|
||||||
|
public Boolean run(ILibVkxService service) {
|
||||||
|
try {
|
||||||
|
return service.isPlaylistCached(playlist_id, owner_id);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean defaultValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isVkxCached(String trackId) {
|
||||||
|
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
||||||
|
@Override
|
||||||
|
public Boolean run(ILibVkxService service) {
|
||||||
|
try {
|
||||||
|
var splits = trackId.split("_");
|
||||||
|
var id = Integer.parseInt(splits[1]);
|
||||||
|
var owner = Integer.parseInt(splits[0]);
|
||||||
|
return service.isTrackCached(id, owner);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean defaultValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
|
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
|
||||||
@@ -138,6 +139,7 @@
|
|||||||
<uses-permission android:name="me.everything.badger.permission.BADGE_COUNT_WRITE" />
|
<uses-permission android:name="me.everything.badger.permission.BADGE_COUNT_WRITE" />
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||||
<uses-permission android:name="com.mgoogle.android.c2dm.permission.RECEIVE" />
|
<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.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
|
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
|
||||||
|
|
||||||
@@ -196,6 +198,14 @@
|
|||||||
android:name="ru.vtosters.lite.ui.activities.VKAdminTokenActivity"
|
android:name="ru.vtosters.lite.ui.activities.VKAdminTokenActivity"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop">
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.guardanis.applock.activities.LockCreationActivity"
|
||||||
|
android:launchMode="singleTop">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.guardanis.applock.activities.UnlockActivity"
|
||||||
|
android:launchMode="singleTop">
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.vtosters.lite.MainActivity"
|
android:name="com.vtosters.lite.MainActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||||
@@ -2313,6 +2323,14 @@
|
|||||||
<action android:name="com.mgoogle.android.c2dm.intent.RECEIVE" />
|
<action android:name="com.mgoogle.android.c2dm.intent.RECEIVE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<receiver
|
||||||
|
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver3"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="app.revanced.android.c2dm.permission.SEND">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="app.revanced.android.c2dm.intent.RECEIVE" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.google.android.gms.common.api.GoogleApiActivity"
|
android:name="com.google.android.gms.common.api.GoogleApiActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:drawable="@color/applock__create_choose_option_bg_pressed" android:state_selected="true"/>
|
||||||
|
<item android:drawable="@color/applock__create_choose_option_bg_pressed" android:state_pressed="true"/>
|
||||||
|
<item android:drawable="@color/applock__create_choose_option_bg" />
|
||||||
|
|
||||||
|
</selector>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/applock__activity_lock_creation"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/applock__activity_padding">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/pin__container"
|
||||||
|
layout="@layout/applock__lock_creation" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/applock__activity_unlock"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/applock__activity_padding">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/pin__container"
|
||||||
|
layout="@layout/applock__unlock" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<EditText
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:imeOptions="flagNoExtractUi|actionDone"
|
||||||
|
android:inputType="number"
|
||||||
|
android:textColor="@android:color/transparent"/>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:applock="http://schemas.android.com/apk/res-auto"
|
||||||
|
style="@style/applock__content_parent"
|
||||||
|
android:descendantFocusability="beforeDescendants"
|
||||||
|
android:focusableInTouchMode="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pin__description"
|
||||||
|
style="@style/applock__description" />
|
||||||
|
|
||||||
|
<com.guardanis.applock.pin.PINInputView
|
||||||
|
android:id="@+id/pin__input_view"
|
||||||
|
style="@style/applock__input_pin_view"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/pin__action_settings"
|
||||||
|
style="@style/applock__action_settings"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:applock="http://schemas.android.com/apk/res-auto"
|
||||||
|
style="@style/applock__content_parent"
|
||||||
|
android:descendantFocusability="beforeDescendants"
|
||||||
|
android:focusableInTouchMode="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pin__description"
|
||||||
|
style="@style/applock__description"/>
|
||||||
|
|
||||||
|
<com.guardanis.applock.pin.PINInputView
|
||||||
|
android:id="@+id/pin__input_view"
|
||||||
|
style="@style/applock__input_pin_view"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/pin__action_settings"
|
||||||
|
style="@style/applock__action_settings"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -6045,7 +6045,7 @@
|
|||||||
<string name="newsfeedlistmanager_summ">Наладкі спісаў навін</string>
|
<string name="newsfeedlistmanager_summ">Наладкі спісаў навін</string>
|
||||||
<string name="newsfeedlistmanager_reset_title">Скінуць спіс навін</string>
|
<string name="newsfeedlistmanager_reset_title">Скінуць спіс навін</string>
|
||||||
<string name="where_is_cache_settings">Дзе налады кэша?</string>
|
<string name="where_is_cache_settings">Дзе налады кэша?</string>
|
||||||
<string name="where_is_cache_settings_summ">Яны пераехалі ў налады мода ва ўкладку Іншае</string>
|
<string name="where_is_cache_settings_summ">Яны пераехалі ў налады мода ва ўкладку Дадзеныя і памяць</string>
|
||||||
<string name="gms_about">Пра GMS</string>
|
<string name="gms_about">Пра GMS</string>
|
||||||
<string name="gms_what_is_title">Што такое GMS?</string>
|
<string name="gms_what_is_title">Што такое GMS?</string>
|
||||||
<string name="gms_what_is_summ">GMS - Google Mobile Services, неабходны кампанент для нармальнай працы прыкладанняў.
|
<string name="gms_what_is_summ">GMS - Google Mobile Services, неабходны кампанент для нармальнай працы прыкладанняў.
|
||||||
@@ -6066,9 +6066,7 @@
|
|||||||
<string name="gms_download_microg">Спампаваць MicroG</string>
|
<string name="gms_download_microg">Спампаваць MicroG</string>
|
||||||
<string name="gms_what_to_do_next">Што рабіць далей?</string>
|
<string name="gms_what_to_do_next">Што рабіць далей?</string>
|
||||||
<string name="gms_what_to_do_after_microg_title">Што рабіць пасля ўстаноўкі MicroG?</string>
|
<string name="gms_what_to_do_after_microg_title">Што рабіць пасля ўстаноўкі MicroG?</string>
|
||||||
<string name="gms_what_to_do_after_microg_summ">1. Увайдзіце ў свой Google акаўнт \n2. Дазвольце VTLite атрымліваць
|
<string name="gms_what_to_do_after_microg_summ">1. Увайдзіце ў свой Google акаўнт \n2. Пераўсталюеце прыкладанне VTLite</string>
|
||||||
апавяшчэнні \n3. Абнавіце рэгістрацыю Firebase у наладах у раздзеле "Іншае"
|
|
||||||
</string>
|
|
||||||
<string name="donate">Дапамагчы праекту</string>
|
<string name="donate">Дапамагчы праекту</string>
|
||||||
<string name="fulltime">dd MMMM yyyy у HH:mm:ss</string>
|
<string name="fulltime">dd MMMM yyyy у HH:mm:ss</string>
|
||||||
<string name="fulltime2">dd MMMM у HH:mm:ss</string>
|
<string name="fulltime2">dd MMMM у HH:mm:ss</string>
|
||||||
@@ -7010,7 +7008,7 @@
|
|||||||
<string name="useGenius_summ">Атрымліваць тэксты з сэрвісу Genius</string>
|
<string name="useGenius_summ">Атрымліваць тэксты з сэрвісу Genius</string>
|
||||||
<string name="useGenius_title">Тэксты з Genius</string>
|
<string name="useGenius_title">Тэксты з Genius</string>
|
||||||
|
|
||||||
<string name="datasettings_title">Налады дадзеных прыкладання</string>
|
<string name="datasettings_title">Дадзеныя і памяць</string>
|
||||||
<string name="invalidate_theme_cache_title">Інвалідацыя кэша тэмы</string>
|
<string name="invalidate_theme_cache_title">Інвалідацыя кэша тэмы</string>
|
||||||
<string name="invalidate_theme_cache_each_update_title">Ачыстка кэша тэмы пасля абнаўлення для прадухілення праблем з акцэнтамі</string>
|
<string name="invalidate_theme_cache_each_update_title">Ачыстка кэша тэмы пасля абнаўлення для прадухілення праблем з акцэнтамі</string>
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user