diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java new file mode 100644 index 00000000..d04d3d94 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java @@ -0,0 +1,39 @@ +package app.revanced.tiktok.feedfilter; + +import com.ss.android.ugc.aweme.feed.model.Aweme; +import com.ss.android.ugc.aweme.feed.model.FeedItemList; + +import java.util.Iterator; +import java.util.List; + +import app.revanced.tiktok.settings.SettingsEnum; + +public class FeedItemsFilter { + + public static void filter(FeedItemList feedItemList) { + if (SettingsEnum.TIK_REMOVE_ADS.getBoolean()) removeAds(feedItemList); + if (SettingsEnum.TIK_HIDE_LIVE.getBoolean()) removeLive(feedItemList); + } + + private static void removeAds(FeedItemList feedItemList) { + List items = feedItemList.items; + Iterator it = items.iterator(); + while (it.hasNext()) { + Aweme item = it.next(); + if (item != null && (item.isAd() || item.isWithPromotionalMusic())) { + it.remove(); + } + } + } + + private static void removeLive(FeedItemList feedItemList) { + List items = feedItemList.items; + Iterator it = items.iterator(); + while (it.hasNext()) { + Aweme item = it.next(); + if (item != null && (item.isLive() || item.isLiveReplay())) { + it.remove(); + } + } + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settings/ReturnType.java b/app/src/main/java/app/revanced/tiktok/settings/ReturnType.java new file mode 100644 index 00000000..bd64c8ea --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settings/ReturnType.java @@ -0,0 +1,5 @@ +package app.revanced.tiktok.settings; + +public enum ReturnType { + BOOLEAN, INTEGER, STRING, LONG, FLOAT +} diff --git a/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java b/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java new file mode 100644 index 00000000..c8ec5783 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java @@ -0,0 +1,148 @@ +package app.revanced.tiktok.settings; + +import android.content.Context; +import android.util.Log; + +import app.revanced.tiktok.utils.LogHelper; +import app.revanced.tiktok.utils.ReVancedUtils; +import app.revanced.tiktok.utils.SharedPrefHelper; + +public enum SettingsEnum { + //TikTok Settings + TIK_REMOVE_ADS("tik-remove-ads", true, SharedPrefHelper.SharedPrefNames.TIKTOK_PREFS, ReturnType.BOOLEAN, true), + TIK_HIDE_LIVE("tik-hide-live", false, SharedPrefHelper.SharedPrefNames.TIKTOK_PREFS, ReturnType.BOOLEAN, true), + TIK_DEBUG("tik_debug", false, SharedPrefHelper.SharedPrefNames.TIKTOK_PREFS, ReturnType.BOOLEAN); + + static { + load(); + } + + private final String path; + private final Object defaultValue; + private final SharedPrefHelper.SharedPrefNames sharedPref; + private final ReturnType returnType; + private final boolean rebootApp; + private Object value = null; + + SettingsEnum(String path, Object defaultValue, ReturnType returnType) { + this.path = path; + this.defaultValue = defaultValue; + this.sharedPref = SharedPrefHelper.SharedPrefNames.TIKTOK_PREFS; + this.returnType = returnType; + this.rebootApp = false; + } + + SettingsEnum(String path, Object defaultValue, SharedPrefHelper.SharedPrefNames prefName, ReturnType returnType) { + this.path = path; + this.defaultValue = defaultValue; + this.sharedPref = prefName; + this.returnType = returnType; + this.rebootApp = false; + } + + SettingsEnum(String path, Object defaultValue, SharedPrefHelper.SharedPrefNames prefName, ReturnType returnType, Boolean rebootApp) { + this.path = path; + this.defaultValue = defaultValue; + this.sharedPref = prefName; + this.returnType = returnType; + this.rebootApp = rebootApp; + } + + private static void load() { + Context context = ReVancedUtils.getAppContext(); + if (context == null) { + Log.e("revanced: SettingsEnum", "Context returned null! Setings NOT initialized"); + } else { + try { + for (SettingsEnum setting : values()) { + Object value = setting.getDefaultValue(); + + //LogHelper is not initialized here + Log.d("revanced: SettingsEnum", "Loading Setting: " + setting.name()); + + switch (setting.getReturnType()) { + case FLOAT: + value = SharedPrefHelper.getFloat(context, setting.sharedPref, setting.getPath(), (float) setting.getDefaultValue()); + break; + case LONG: + value = SharedPrefHelper.getLong(context, setting.sharedPref, setting.getPath(), (long) setting.getDefaultValue()); + break; + case BOOLEAN: + value = SharedPrefHelper.getBoolean(context, setting.sharedPref, setting.getPath(), (boolean) setting.getDefaultValue()); + break; + case INTEGER: + value = SharedPrefHelper.getInt(context, setting.sharedPref, setting.getPath(), (int) setting.getDefaultValue()); + break; + case STRING: + value = SharedPrefHelper.getString(context, setting.sharedPref, setting.getPath(), (String) setting.getDefaultValue()); + break; + default: + LogHelper.printException(SettingsEnum.class, "Setting does not have a valid Type. Name is: " + setting.name()); + break; + } + setting.setValue(value); + + //LogHelper is not initialized here + Log.d("revanced: SettingsEnum", "Loaded Setting: " + setting.name() + " Value: " + value); + } + } catch (Throwable th) { + LogHelper.printException(SettingsEnum.class, "Error during load()!", th); + } + } + } + + public void setValue(Object newValue) { + this.value = newValue; + } + + public void saveValue(Object newValue) { + Context context = ReVancedUtils.getAppContext(); + if (context != null) { + if (returnType == ReturnType.BOOLEAN) { + SharedPrefHelper.saveBoolean(context, sharedPref, path, (Boolean) newValue); + } else { + SharedPrefHelper.saveString(context, sharedPref, path, newValue + ""); + } + value = newValue; + } else { + LogHelper.printException(SettingsEnum.class, "Context on SaveValue is null!"); + } + } + + public int getInt() { + return (int) value; + } + + public String getString() { + return (String) value; + } + + public boolean getBoolean() { + return (Boolean) value; + } + + public Long getLong() { + return (Long) value; + } + + public Float getFloat() { + return (Float) value; + } + + public Object getDefaultValue() { + return defaultValue; + } + + public String getPath() { + return path; + } + + public ReturnType getReturnType() { + return returnType; + } + + public boolean shouldRebootOnChange() { + return rebootApp; + } + +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java new file mode 100644 index 00000000..69b0849a --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java @@ -0,0 +1,134 @@ +package app.revanced.tiktok.settingsmenu; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Process; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; + +import androidx.annotation.Nullable; + +import com.ss.android.ugc.aweme.splash.SplashActivity; + +import app.revanced.tiktok.settings.SettingsEnum; +import app.revanced.tiktok.utils.ReVancedUtils; +import app.revanced.tiktok.utils.SharedPrefHelper; + +public class ReVancedSettingsFragment extends PreferenceFragment { + + private boolean Registered = false; + private boolean settingsInitialized = false; + + SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { + for (SettingsEnum setting : SettingsEnum.values()) { + if (!setting.getPath().equals(str)) continue; + + if (ReVancedUtils.getAppContext() != null && this.settingsInitialized && setting.shouldRebootOnChange()) { + rebootDialog(getActivity()); + } + } + }; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getPreferenceManager().setSharedPreferencesName(SharedPrefHelper.SharedPrefNames.TIKTOK_PREFS.getName()); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this.listener); + this.Registered = true; + + final Activity context = this.getActivity(); + PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context); + setPreferenceScreen(preferenceScreen); + + //Feed filter + if (SettingsStatus.feedFilter) { + PreferenceCategory feedFilter = new PreferenceCategory(context); + feedFilter.setTitle("Feed filter"); + preferenceScreen.addPreference(feedFilter); + + //Remove ads toggle + { + SwitchPreference preference = new SwitchPreference(context); + feedFilter.addPreference(preference); + preference.setKey(SettingsEnum.TIK_REMOVE_ADS.getPath()); + preference.setDefaultValue(SettingsEnum.TIK_REMOVE_ADS.getDefaultValue()); + preference.setChecked(SettingsEnum.TIK_REMOVE_ADS.getBoolean()); + preference.setTitle("Remove feed ads"); + preference.setSummary("Remove ads from feed."); + preference.setOnPreferenceChangeListener((pref, newValue) -> { + final boolean value = (Boolean) newValue; + SettingsEnum.TIK_REMOVE_ADS.saveValue(value); + return true; + }); + } + //Hide LiveStreams toggle + { + SwitchPreference preference = new SwitchPreference(context); + feedFilter.addPreference(preference); + preference.setKey(SettingsEnum.TIK_HIDE_LIVE.getPath()); + preference.setDefaultValue(SettingsEnum.TIK_HIDE_LIVE.getDefaultValue()); + preference.setChecked(SettingsEnum.TIK_HIDE_LIVE.getBoolean()); + preference.setTitle("Hide livestreams"); + preference.setSummary("Hide livestreams from feed."); + preference.setOnPreferenceChangeListener((pref, newValue) -> { + final boolean value = (Boolean) newValue; + SettingsEnum.TIK_HIDE_LIVE.saveValue(value); + return true; + }); + } + } + + //Integration + PreferenceCategory integration = new PreferenceCategory(context); + integration.setTitle("Integration"); + preferenceScreen.addPreference(integration); + //Enable DebugLog toggle + { + SwitchPreference preference = new SwitchPreference(context); + integration.addPreference(preference); + preference.setKey(SettingsEnum.TIK_DEBUG.getPath()); + preference.setDefaultValue(SettingsEnum.TIK_DEBUG.getDefaultValue()); + preference.setChecked(SettingsEnum.TIK_DEBUG.getBoolean()); + preference.setTitle("Enable debug log"); + preference.setSummary("Show integration debug log."); + preference.setOnPreferenceChangeListener((pref, newValue) -> { + final boolean value = (Boolean) newValue; + SettingsEnum.TIK_DEBUG.saveValue(value); + return true; + }); + } + this.settingsInitialized = true; + } + + @Override // android.preference.PreferenceFragment, android.app.Fragment + public void onDestroy() { + if (this.Registered) { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this.listener); + this.Registered = false; + } + super.onDestroy(); + } + + private void reboot(Activity activity) { + int intent; + intent = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; + ((AlarmManager) activity.getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.ELAPSED_REALTIME, 1500L, PendingIntent.getActivity(activity, 0, new Intent(activity, SplashActivity.class), intent)); + Process.killProcess(Process.myPid()); + } + + private void rebootDialog(final Activity activity) { + new AlertDialog.Builder(activity). + setMessage("Refresh and restart"). + setPositiveButton("RESTART", (dialog, i) -> reboot(activity)) + .setNegativeButton("CANCEL", null) + .show(); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java new file mode 100644 index 00000000..7449ed8a --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java @@ -0,0 +1,44 @@ +package app.revanced.tiktok.settingsmenu; + +import android.content.Context; +import android.content.Intent; +import android.preference.PreferenceFragment; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import com.bytedance.ies.ugc.aweme.commercialize.compliance.personalization.AdPersonalizationActivity; + +import app.revanced.tiktok.utils.LogHelper; +import app.revanced.tiktok.utils.ReVancedUtils; + + +public class SettingsMenu { + public static void initializeSettings(AdPersonalizationActivity base) { + SettingsStatus.load(); + LinearLayout linearLayout = new LinearLayout(base); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setFitsSystemWindows(true); + linearLayout.setTransitionGroup(true); + FrameLayout fragment = new FrameLayout(base); + fragment.setLayoutParams(new FrameLayout.LayoutParams(-1, -1)); + int fragmentId = View.generateViewId(); + fragment.setId(fragmentId); + linearLayout.addView(fragment); + base.setContentView(linearLayout); + PreferenceFragment preferenceFragment = new ReVancedSettingsFragment(); + base.getFragmentManager().beginTransaction().replace(fragmentId, preferenceFragment).commit(); + } + + public static void startSettingsActivity() { + Context appContext = ReVancedUtils.getAppContext(); + if (appContext != null) { + Intent intent = new Intent(appContext, AdPersonalizationActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + appContext.startActivity(intent); + } else { + LogHelper.debug(SettingsMenu.class, "ReVancedUtils.getAppContext() return null"); + } + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java new file mode 100644 index 00000000..16efc3cc --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java @@ -0,0 +1,11 @@ +package app.revanced.tiktok.settingsmenu; + +public class SettingsStatus { + public static boolean feedFilter = false; + public static void enableFeedFilter() { + feedFilter = true; + } + public static void load() { + + } +} diff --git a/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java b/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java new file mode 100644 index 00000000..8707d839 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java @@ -0,0 +1,28 @@ +package app.revanced.tiktok.utils; + +import android.util.Log; + +import app.revanced.tiktok.settings.SettingsEnum; + +public class LogHelper { + + //ToDo: Get Calling classname using Reflection + + public static void debug(Class clazz, String message) { + if (SettingsEnum.TIK_DEBUG.getBoolean()) { + Log.d("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message); + } + } + + public static void printException(Class clazz, String message, Throwable ex) { + Log.e("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message, ex); + } + + public static void printException(Class clazz, String message) { + Log.e("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message); + } + + public static void info(Class clazz, String message) { + Log.i("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java b/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java new file mode 100644 index 00000000..7ac089ff --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java @@ -0,0 +1,18 @@ +package app.revanced.tiktok.utils; + +import android.content.Context; + +public class ReVancedUtils { + + //Used by TiktokIntegrations patch + public static Context context; + + //Used by TiktokIntegrations patch + public static Context getAppContext() { + if (context != null) { + return context; + } + LogHelper.printException(ReVancedUtils.class, "Context is null!"); + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/tiktok/utils/SharedPrefHelper.java b/app/src/main/java/app/revanced/tiktok/utils/SharedPrefHelper.java new file mode 100644 index 00000000..ff9dd0ec --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/utils/SharedPrefHelper.java @@ -0,0 +1,82 @@ +package app.revanced.tiktok.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +public class SharedPrefHelper { + public static void saveString(Context context, SharedPrefNames prefName, String key, String value) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + sharedPreferences.edit().putString(key, value).apply(); + } + + public static void saveBoolean(Context context, SharedPrefNames prefName, String key, Boolean value) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + sharedPreferences.edit().putBoolean(key, value).apply(); + } + + public static String getString(Context context, SharedPrefNames prefName, String key, String _default) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + return (sharedPreferences.getString(key, _default)); + } + + public static Boolean getBoolean(Context context, SharedPrefNames prefName, String key, Boolean _default) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + return (sharedPreferences.getBoolean(key, _default)); + } + + public static Long getLong(Context context, SharedPrefNames prefName, String key, Long _default) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + try { + return Long.valueOf(sharedPreferences.getString(key, _default + "")); + } catch (ClassCastException ex) { + return sharedPreferences.getLong(key, _default); + } + } + + public static Float getFloat(Context context, SharedPrefNames prefName, String key, Float _default) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + try { + return Float.valueOf(sharedPreferences.getString(key, _default + "")); + } catch (ClassCastException ex) { + return sharedPreferences.getFloat(key, _default); + } + } + + public static Integer getInt(Context context, SharedPrefNames prefName, String key, Integer _default) { + SharedPreferences sharedPreferences = getPreferences(context, prefName); + try { + return Integer.valueOf(sharedPreferences.getString(key, _default + "")); + } catch (ClassCastException ex) { + return sharedPreferences.getInt(key, _default); + } + } + + public static SharedPreferences getPreferences(Context context, SharedPrefNames name) { + if (context == null) return null; + return context.getSharedPreferences(name.getName(), Context.MODE_PRIVATE); + } + + public static SharedPreferences getPreferences(Context context, String name) { + if (context == null) return null; + return context.getSharedPreferences(name, Context.MODE_PRIVATE); + } + + public enum SharedPrefNames { + TIKTOK_PREFS("tiktok_revanced"); + + private final String name; + + SharedPrefNames(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/app/src/main/java/com/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity.java b/app/src/main/java/com/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity.java new file mode 100644 index 00000000..ecf204d2 --- /dev/null +++ b/app/src/main/java/com/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity.java @@ -0,0 +1,6 @@ +package com.bytedance.ies.ugc.aweme.commercialize.compliance.personalization; + +import android.app.Activity; + +//Dummy class +public class AdPersonalizationActivity extends Activity { } diff --git a/app/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java b/app/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java new file mode 100644 index 00000000..82f0fc5d --- /dev/null +++ b/app/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java @@ -0,0 +1,18 @@ +package com.ss.android.ugc.aweme.feed.model; + +//Dummy class +public class Aweme { + public boolean isAd() { + return true; + } + public boolean isLive() { + return true; + } + public boolean isLiveReplay() { + return true; + } + public boolean isWithPromotionalMusic() { + return true; + } + +} diff --git a/app/src/main/java/com/ss/android/ugc/aweme/feed/model/FeedItemList.java b/app/src/main/java/com/ss/android/ugc/aweme/feed/model/FeedItemList.java new file mode 100644 index 00000000..139159ea --- /dev/null +++ b/app/src/main/java/com/ss/android/ugc/aweme/feed/model/FeedItemList.java @@ -0,0 +1,8 @@ +package com.ss.android.ugc.aweme.feed.model; + +import java.util.List; + +//Dummy class +public class FeedItemList { + public List items; +} diff --git a/app/src/main/java/com/ss/android/ugc/aweme/splash/SplashActivity.java b/app/src/main/java/com/ss/android/ugc/aweme/splash/SplashActivity.java new file mode 100644 index 00000000..ec7038a6 --- /dev/null +++ b/app/src/main/java/com/ss/android/ugc/aweme/splash/SplashActivity.java @@ -0,0 +1,9 @@ +package com.ss.android.ugc.aweme.splash; + +import android.annotation.SuppressLint; +import android.app.Activity; + +//Dummy class +@SuppressLint("CustomSplashScreen") +public class SplashActivity extends Activity { +}