mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-11-19 02:19:23 +01:00
fix(YouTube - Return YouTube Dislike): Prevent the first Short opened from freezing the UI (#532)
This commit is contained in:
parent
d484f35127
commit
0bb86694e2
@ -9,6 +9,7 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch;
|
import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch;
|
||||||
|
import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch;
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.shared.PlayerType;
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
@ -27,19 +28,25 @@ import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislik
|
|||||||
* Handles all interaction of UI patch components.
|
* Handles all interaction of UI patch components.
|
||||||
*
|
*
|
||||||
* Known limitation:
|
* Known limitation:
|
||||||
* Litho based Shorts player can experience temporarily frozen video playback if the RYD fetch takes too long.
|
* The implementation of Shorts litho requires blocking the loading the first Short until RYD has completed.
|
||||||
|
* This is because it modifies the dislikes text synchronously, and if the RYD fetch has
|
||||||
|
* not completed yet then the UI will be temporarily frozen.
|
||||||
*
|
*
|
||||||
* Temporary work around:
|
* A (yet to be implemented) solution that fixes this problem. Any one of:
|
||||||
* Enable app spoofing to version 18.33.40 or older, as that uses a non litho Shorts player.
|
* - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes text asynchronously.
|
||||||
*
|
* - Find a way to force Litho to rebuild it's component tree,
|
||||||
* Permanent fix (yet to be implemented), either of:
|
* and use that hook to force the shorts dislikes to update after the fetch is completed.
|
||||||
* - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes asynchronously.
|
* - Hook into the dislikes button image view, and replace the dislikes thumb down image with a
|
||||||
* - Find a way to force Litho to rebuild it's component tree
|
* generated image of the number of dislikes, then update the image asynchronously. This Could
|
||||||
* (and use that hook to force the shorts dislikes to update after the fetch is completed).
|
* also be used for the regular video player to give a better UI layout and completely remove
|
||||||
|
* the need for the Rolling Number patches.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ReturnYouTubeDislikePatch {
|
public class ReturnYouTubeDislikePatch {
|
||||||
|
|
||||||
|
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
||||||
|
SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RYD data for the current video on screen.
|
* RYD data for the current video on screen.
|
||||||
*/
|
*/
|
||||||
@ -549,26 +556,46 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
// Video Id and voting hooks (all players).
|
// Video Id and voting hooks (all players).
|
||||||
//
|
//
|
||||||
|
|
||||||
|
private static volatile boolean lastPlayerResponseWasShort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
||||||
*/
|
*/
|
||||||
public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) {
|
public static void preloadVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||||
try {
|
try {
|
||||||
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||||
// and the 'is opening/playing' parameter will be false.
|
|
||||||
// This hook will be called again when the Short is actually opened.
|
|
||||||
if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_ENABLED.getBoolean()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (videoId.equals(lastPrefetchedVideoId)) {
|
if (videoId.equals(lastPrefetchedVideoId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean videoIdIsShort = VideoInformation.lastVideoIdIsShort();
|
||||||
|
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
||||||
|
// and the 'is opening/playing' parameter will be false.
|
||||||
|
// This hook will be called again when the Short is actually opened.
|
||||||
|
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||||
|
&& videoIdIsShort && !lastPlayerResponseWasShort;
|
||||||
|
lastPlayerResponseWasShort = videoIdIsShort;
|
||||||
lastPrefetchedVideoId = videoId;
|
lastPrefetchedVideoId = videoId;
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId);
|
LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId);
|
||||||
ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||||
|
if (waitForFetchToComplete && !fetch.fetchCompleted()) {
|
||||||
|
// This call is off the main thread, so wait until the RYD fetch completely finishes,
|
||||||
|
// otherwise if this returns before the fetch completes then the UI can
|
||||||
|
// become frozen when the main thread tries to modify the litho Shorts dislikes and
|
||||||
|
// it must wait for the fetch.
|
||||||
|
// Only need to do this for the first Short opened, as the next Short to swipe to
|
||||||
|
// are preloaded in the background.
|
||||||
|
//
|
||||||
|
// If an asynchronous litho Shorts solution is found, then this blocking call should be removed.
|
||||||
|
LogHelper.printDebug(() -> "Waiting for prefetch to complete: " + videoId);
|
||||||
|
fetch.getFetchData(10000); // Use any arbitrarily large max wait time.
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "preloadVideoId failure", ex);
|
LogHelper.printException(() -> "preloadVideoId failure", ex);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@ import java.util.Objects;
|
|||||||
public final class VideoInformation {
|
public final class VideoInformation {
|
||||||
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
||||||
private static final String SEEK_METHOD_NAME = "seekTo";
|
private static final String SEEK_METHOD_NAME = "seekTo";
|
||||||
|
/**
|
||||||
|
* Prefix present in all Short player parameters signature.
|
||||||
|
*/
|
||||||
|
private static final String SHORTS_PLAYER_PARAMETERS = "8AEB";
|
||||||
|
|
||||||
private static WeakReference<Object> playerControllerRef;
|
private static WeakReference<Object> playerControllerRef;
|
||||||
private static Method seekMethod;
|
private static Method seekMethod;
|
||||||
@ -28,6 +32,7 @@ public final class VideoInformation {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static volatile String playerResponseVideoId = "";
|
private static volatile String playerResponseVideoId = "";
|
||||||
|
private static volatile boolean videoIdIsShort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current playback speed
|
* The current playback speed
|
||||||
@ -65,12 +70,33 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the player parameters are for a Short.
|
||||||
|
*/
|
||||||
|
public static boolean playerParametersAreShort(@NonNull String parameters) {
|
||||||
|
return parameters.startsWith(SHORTS_PLAYER_PARAMETERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) {
|
||||||
|
final boolean isShort = playerParametersAreShort(signature);
|
||||||
|
if (!isShort || isShortAndOpeningOrPlaying) {
|
||||||
|
if (videoIdIsShort != isShort) {
|
||||||
|
videoIdIsShort = isShort;
|
||||||
|
LogHelper.printDebug(() -> "videoIdIsShort: " + isShort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return signature; // Return the original value since we are observing and not modifying.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread.
|
* Injection point. Called off the main thread.
|
||||||
*
|
*
|
||||||
* @param videoId The id of the last video loaded.
|
* @param videoId The id of the last video loaded.
|
||||||
*/
|
*/
|
||||||
public static void setPlayerResponseVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) {
|
public static void setPlayerResponseVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||||
if (!playerResponseVideoId.equals(videoId)) {
|
if (!playerResponseVideoId.equals(videoId)) {
|
||||||
LogHelper.printDebug(() -> "New player response video id: " + videoId);
|
LogHelper.printDebug(() -> "New player response video id: " + videoId);
|
||||||
playerResponseVideoId = videoId;
|
playerResponseVideoId = videoId;
|
||||||
@ -155,9 +181,9 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Id of the current video playing. Includes Shorts.
|
* Id of the last video opened. Includes Shorts.
|
||||||
*
|
*
|
||||||
* @return The id of the video. Empty string if not set yet.
|
* @return The id of the video, or an empty string if no videos have been opened yet.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getVideoId() {
|
public static String getVideoId() {
|
||||||
@ -166,20 +192,30 @@ public final class VideoInformation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Differs from {@link #videoId} as this is the video id for the
|
* Differs from {@link #videoId} as this is the video id for the
|
||||||
* last player response received, which may not be the current video playing.
|
* last player response received, which may not be the last video opened.
|
||||||
* <p>
|
* <p>
|
||||||
* If Shorts are loading the background, this commonly will be
|
* If Shorts are loading the background, this commonly will be
|
||||||
* different from the Short that is currently on screen.
|
* different from the Short that is currently on screen.
|
||||||
* <p>
|
* <p>
|
||||||
* For most use cases, you should instead use {@link #getVideoId()}.
|
* For most use cases, you should instead use {@link #getVideoId()}.
|
||||||
*
|
*
|
||||||
* @return The id of the last video loaded. Empty string if not set yet.
|
* @return The id of the last video loaded, or an empty string if no videos have been loaded yet.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getPlayerResponseVideoId() {
|
public static String getPlayerResponseVideoId() {
|
||||||
return playerResponseVideoId;
|
return playerResponseVideoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the last player response video id _that was opened_ was a Short.
|
||||||
|
* <p>
|
||||||
|
* Note: This value returned may not match the status of {@link #getPlayerResponseVideoId()}
|
||||||
|
* since that includes player responses for videos not opened.
|
||||||
|
*/
|
||||||
|
public static boolean lastVideoIdIsShort() {
|
||||||
|
return videoIdIsShort;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current playback speed.
|
* @return The current playback speed.
|
||||||
*/
|
*/
|
||||||
|
@ -53,14 +53,14 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void newPlayerResponseVideoId(String videoId, boolean videoIsOpeningOrPlaying) {
|
public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||||
try {
|
try {
|
||||||
if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
if (!isShortAndOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (lastVideoIds) {
|
synchronized (lastVideoIds) {
|
||||||
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) {
|
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) {
|
||||||
LogHelper.printDebug(() -> "New video id: " + videoId);
|
LogHelper.printDebug(() -> "New Short video id: " + videoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -37,11 +37,6 @@ public class SpoofSignaturePatch {
|
|||||||
*/
|
*/
|
||||||
private static final String SCRIM_PARAMETER = "SAFgAXgB";
|
private static final String SCRIM_PARAMETER = "SAFgAXgB";
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters used in YouTube Shorts.
|
|
||||||
*/
|
|
||||||
private static final String SHORTS_PLAYER_PARAMETERS = "8AEB";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last video id loaded. Used to prevent reloading the same spec multiple times.
|
* Last video id loaded. Used to prevent reloading the same spec multiple times.
|
||||||
*/
|
*/
|
||||||
@ -62,7 +57,7 @@ public class SpoofSignaturePatch {
|
|||||||
*
|
*
|
||||||
* @param parameters Original protobuf parameter value.
|
* @param parameters Original protobuf parameter value.
|
||||||
*/
|
*/
|
||||||
public static String spoofParameter(String parameters) {
|
public static String spoofParameter(String parameters, boolean isShortAndOpeningOrPlaying) {
|
||||||
try {
|
try {
|
||||||
LogHelper.printDebug(() -> "Original protobuf parameter value: " + parameters);
|
LogHelper.printDebug(() -> "Original protobuf parameter value: " + parameters);
|
||||||
|
|
||||||
@ -74,7 +69,7 @@ public class SpoofSignaturePatch {
|
|||||||
if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters;
|
if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters;
|
||||||
|
|
||||||
// Shorts do not need to be spoofed.
|
// Shorts do not need to be spoofed.
|
||||||
if (useOriginalStoryboardRenderer = parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) {
|
if (useOriginalStoryboardRenderer = VideoInformation.playerParametersAreShort(parameters)) {
|
||||||
isPlayingShorts = true;
|
isPlayingShorts = true;
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
@ -62,12 +62,12 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
* How long to wait until API calls are resumed, if the API requested a back off.
|
* How long to wait until API calls are resumed, if the API requested a back off.
|
||||||
* No clear guideline of how long to wait until resuming.
|
* No clear guideline of how long to wait until resuming.
|
||||||
*/
|
*/
|
||||||
private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 4 * 60 * 1000; // 4 Minutes.
|
private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How long to wait until API calls are resumed, if any connection error occurs.
|
* How long to wait until API calls are resumed, if any connection error occurs.
|
||||||
*/
|
*/
|
||||||
private static final int BACKOFF_CONNECTION_ERROR_MILLISECONDS = 60 * 1000; // 60 Seconds.
|
private static final int BACKOFF_CONNECTION_ERROR_MILLISECONDS = 2 * 60 * 1000; // 2 Minutes.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If non zero, then the system time of when API calls can resume.
|
* If non zero, then the system time of when API calls can resume.
|
||||||
|
@ -13,7 +13,6 @@ import android.preference.PreferenceScreen;
|
|||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.ReturnYouTubeDislikePatch;
|
import app.revanced.integrations.patches.ReturnYouTubeDislikePatch;
|
||||||
import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch;
|
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
@ -21,9 +20,6 @@ import app.revanced.integrations.settings.SharedPrefCategory;
|
|||||||
|
|
||||||
public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
private static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
|
||||||
SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If dislikes are shown on Shorts.
|
* If dislikes are shown on Shorts.
|
||||||
*/
|
*/
|
||||||
@ -79,7 +75,7 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
|||||||
shortsPreference.setChecked(SettingsEnum.RYD_SHORTS.getBoolean());
|
shortsPreference.setChecked(SettingsEnum.RYD_SHORTS.getBoolean());
|
||||||
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
||||||
String shortsSummary = str("revanced_ryd_shorts_summary_on",
|
String shortsSummary = str("revanced_ryd_shorts_summary_on",
|
||||||
IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||||
? ""
|
? ""
|
||||||
: "\n\n" + str("revanced_ryd_shorts_summary_disclaimer"));
|
: "\n\n" + str("revanced_ryd_shorts_summary_disclaimer"));
|
||||||
shortsPreference.setSummaryOn(shortsSummary);
|
shortsPreference.setSummaryOn(shortsSummary);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package app.revanced.integrations.shared
|
package app.revanced.integrations.shared
|
||||||
|
|
||||||
|
import app.revanced.integrations.patches.VideoInformation
|
||||||
import app.revanced.integrations.utils.Event
|
import app.revanced.integrations.utils.Event
|
||||||
import app.revanced.integrations.utils.LogHelper
|
import app.revanced.integrations.utils.LogHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WatchWhile player type
|
* WatchWhile player type.
|
||||||
*/
|
*/
|
||||||
enum class PlayerType {
|
enum class PlayerType {
|
||||||
/**
|
/**
|
||||||
@ -83,6 +84,8 @@ enum class PlayerType {
|
|||||||
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
|
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
|
||||||
* or while watching a short with a regular video present on a spoofed 16.x version of YouTube.
|
* or while watching a short with a regular video present on a spoofed 16.x version of YouTube.
|
||||||
* To include those situations instead use [isNoneHiddenOrMinimized].
|
* To include those situations instead use [isNoneHiddenOrMinimized].
|
||||||
|
*
|
||||||
|
* @see VideoInformation
|
||||||
*/
|
*/
|
||||||
fun isNoneOrHidden(): Boolean {
|
fun isNoneOrHidden(): Boolean {
|
||||||
return this == NONE || this == HIDDEN
|
return this == NONE || this == HIDDEN
|
||||||
@ -99,6 +102,7 @@ enum class PlayerType {
|
|||||||
* though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]).
|
* though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]).
|
||||||
*
|
*
|
||||||
* @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state.
|
* @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state.
|
||||||
|
* @see VideoInformation
|
||||||
*/
|
*/
|
||||||
fun isNoneHiddenOrSlidingMinimized(): Boolean {
|
fun isNoneHiddenOrSlidingMinimized(): Boolean {
|
||||||
return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED
|
return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED
|
||||||
@ -117,6 +121,7 @@ enum class PlayerType {
|
|||||||
*
|
*
|
||||||
* @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state,
|
* @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state,
|
||||||
* a regular video is minimized (and a new video is not being opened).
|
* a regular video is minimized (and a new video is not being opened).
|
||||||
|
* @see VideoInformation
|
||||||
*/
|
*/
|
||||||
fun isNoneHiddenOrMinimized(): Boolean {
|
fun isNoneHiddenOrMinimized(): Boolean {
|
||||||
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
|
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
|
||||||
|
Loading…
Reference in New Issue
Block a user