Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c4f5fec authored by Riddle Hsu's avatar Riddle Hsu Committed by Android (Google) Code Review
Browse files

Merge "Apply perf session for regular window animation" into main

parents fbb66eab ef4353eb
Loading
Loading
Loading
Loading
+94 −23
Original line number Diff line number Diff line
@@ -29,11 +29,11 @@ import android.content.Context;
import android.os.PerformanceHintManager;
import android.os.Trace;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceControl;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

/**
@@ -45,6 +45,7 @@ import java.util.function.Supplier;
public class SystemPerformanceHinter {
    private static final String TAG = "SystemPerformanceHinter";

    private static final int HINT_NO_OP = 0;
    // Change app and SF wakeup times to allow sf more time to composite a frame
    public static final int HINT_SF_EARLY_WAKEUP = 1 << 0;
    // Force max refresh rate
@@ -88,14 +89,17 @@ public class SystemPerformanceHinter {
        private final @HintFlags int hintFlags;
        private final String reason;
        private final int displayId;
        private final int traceCookie;
        private String mTraceName;

        protected HighPerfSession(@HintFlags int hintFlags, int displayId, @NonNull String reason) {
            this.hintFlags = hintFlags;
            this.reason = reason;
            this.displayId = displayId;
            this.traceCookie = new Random().nextInt();
            if (hintFlags != 0) {
        }

        /** Makes this session active. It is no-op if this session is already active. */
        public void start() {
            if (!mActiveSessions.contains(this)) {
                startSession(this);
            }
        }
@@ -103,15 +107,36 @@ public class SystemPerformanceHinter {
        /**
         * Closes this session.
         */
        @Override
        public void close() {
            if (hintFlags != 0) {
            endSession(this);
        }
        }

        @Override
        public void finalize() {
            close();
        }

        boolean asyncTraceBegin() {
            if (!Trace.isTagEnabled(mTraceTag)) {
                mTraceName = null;
                return false;
            }
            if (mTraceName == null) {
                mTraceName = "PerfSession-d" + displayId + "-" + reason;
            }
            Trace.asyncTraceForTrackBegin(mTraceTag, TAG, mTraceName,
                    System.identityHashCode(this));
            return true;
        }

        boolean asyncTraceEnd() {
            if (mTraceName == null) {
                return false;
            }
            Trace.asyncTraceForTrackEnd(mTraceTag, TAG, System.identityHashCode(this));
            return true;
        }
    }

    /**
@@ -119,14 +144,22 @@ public class SystemPerformanceHinter {
     */
    private class NoOpHighPerfSession extends HighPerfSession {
        public NoOpHighPerfSession() {
            super(0 /* hintFlags */, -1 /* displayId */, "");
            super(HINT_NO_OP, Display.INVALID_DISPLAY, "");
        }

        @Override
        public void start() {
        }

        @Override
        public void close() {
            // Do nothing
        }
    }

    /** The tag category of trace. */
    public long mTraceTag = Trace.TRACE_TAG_APP;

    // The active sessions
    private final ArrayList<HighPerfSession> mActiveSessions = new ArrayList<>();
    private final SurfaceControl.Transaction mTransaction;
@@ -134,7 +167,6 @@ public class SystemPerformanceHinter {
    private @Nullable PerformanceHintManager.Session mAdpfSession;
    private @Nullable DisplayRootProvider mDisplayRootProvider;


    /**
     * Constructor for the hinter.
     * @hide
@@ -167,12 +199,12 @@ public class SystemPerformanceHinter {
        mAdpfSession = adpfSession;
    }

    /**
     * Starts a session that requires high performance.
     * @hide
     */
    public HighPerfSession startSession(@HintFlags int hintFlags, int displayId,
    /** Creates a session that requires high performance. */
    public HighPerfSession createSession(@HintFlags int hintFlags, int displayId,
            @NonNull String reason) {
        if (hintFlags == HINT_NO_OP) {
            throw new IllegalArgumentException("Not allow empty hint flags");
        }
        if (mDisplayRootProvider == null && (hintFlags & HINT_SF_FRAME_RATE) != 0) {
            throw new IllegalArgumentException(
                    "Using SF frame rate hints requires a valid display root provider");
@@ -193,9 +225,20 @@ public class SystemPerformanceHinter {
    }

    /**
     * Starts a session that requires high performance.
     * Starts a new session that requires high performance.
     */
    public HighPerfSession startSession(@HintFlags int hintFlags, int displayId,
            @NonNull String reason) {
        final HighPerfSession session = createSession(hintFlags, displayId, reason);
        if (session.hintFlags != HINT_NO_OP) {
            startSession(session);
        }
        return session;
    }

    /** Starts the session that requires high performance. */
    private void startSession(HighPerfSession session) {
        final boolean isTraceEnabled = session.asyncTraceBegin();
        int oldGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL);
        int oldPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY,
                session.displayId);
@@ -217,19 +260,24 @@ public class SystemPerformanceHinter {
            mTransaction.setFrameRateCategory(
                    displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
            transactionChanged = true;
            Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
                    + session.reason, session.traceCookie);
            if (isTraceEnabled) {
                asyncTraceBegin(HINT_SF_FRAME_RATE, session.displayId);
            }
        }

        // Global flags
        if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
            mTransaction.setEarlyWakeupStart();
            transactionChanged = true;
            Trace.beginAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie);
            if (isTraceEnabled) {
                asyncTraceBegin(HINT_SF_EARLY_WAKEUP, Display.INVALID_DISPLAY);
            }
        }
        if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
            mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP);
            Trace.beginAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie);
            if (isTraceEnabled) {
                asyncTraceBegin(HINT_ADPF, Display.INVALID_DISPLAY);
            }
        }
        if (transactionChanged) {
            mTransaction.applyAsyncUnsafe();
@@ -240,6 +288,7 @@ public class SystemPerformanceHinter {
     * Ends a session that requires high performance.
     */
    private void endSession(HighPerfSession session) {
        final boolean isTraceEnabled = session.asyncTraceEnd();
        int oldGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL);
        int oldPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY,
                session.displayId);
@@ -261,19 +310,24 @@ public class SystemPerformanceHinter {
            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
                    /* smoothSwitchOnly= */ false);
            transactionChanged = true;
            Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
                    session.traceCookie);
            if (isTraceEnabled) {
                asyncTraceEnd(HINT_SF_FRAME_RATE);
            }
        }

        // Global flags
        if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
            mTransaction.setEarlyWakeupEnd();
            transactionChanged = true;
            Trace.endAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie);
            if (isTraceEnabled) {
                asyncTraceEnd(HINT_SF_EARLY_WAKEUP);
            }
        }
        if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
            mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
            Trace.endAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie);
            if (isTraceEnabled) {
                asyncTraceEnd(HINT_ADPF);
            }
        }
        if (transactionChanged) {
            mTransaction.applyAsyncUnsafe();
@@ -323,6 +377,23 @@ public class SystemPerformanceHinter {
        return flags;
    }

    private void asyncTraceBegin(@HintFlags int flag, int displayId) {
        final String prefix = switch (flag) {
            case HINT_SF_EARLY_WAKEUP -> "PerfHint-early_wakeup";
            case HINT_SF_FRAME_RATE -> "PerfHint-framerate";
            case HINT_ADPF -> "PerfHint-adpf";
            default -> "PerfHint-" + flag;
        };
        final String name = displayId != Display.INVALID_DISPLAY
                ? (prefix + "-d" + displayId) : prefix;
        Trace.asyncTraceForTrackBegin(mTraceTag, TAG, name,
                flag ^ System.identityHashCode(this));
    }

    private void asyncTraceEnd(@HintFlags int flag) {
        Trace.asyncTraceForTrackEnd(mTraceTag, TAG, flag ^ System.identityHashCode(this));
    }

    /**
     * Dumps the existing sessions.
     */
+50 −0
Original line number Diff line number Diff line
@@ -160,6 +160,7 @@ import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
import static com.android.window.flags.Flags.explicitRefreshRateHints;

import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -245,6 +246,7 @@ import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.SystemPerformanceHinter;
import android.window.TransitionRequestInfo;

import com.android.internal.R;
@@ -571,6 +573,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp

    private final SurfaceSession mSession = new SurfaceSession();

    /**
     * A perf hint session which will boost the refresh rate for the display and change sf duration
     * to handle larger workloads.
     */
    private SystemPerformanceHinter.HighPerfSession mTransitionPrefSession;

    /** A perf hint session which will boost the refresh rate. */
    private SystemPerformanceHinter.HighPerfSession mHighFrameRateSession;

    /**
     * Window that is currently interacting with the user. This window is responsible for receiving
     * key events and pointer events from the user.
@@ -761,6 +772,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
     */
    boolean mDontMoveToTop;

    /** Whether this display contains a WindowContainer with running SurfaceAnimator. */
    boolean mLastContainsRunningSurfaceAnimator;

    /** Used for windows that want to keep the screen awake. */
    private PowerManager.WakeLock mHoldScreenWakeLock;

@@ -3447,6 +3461,42 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
        return dockFrame.bottom - imeFrame.top;
    }

    void enableHighPerfTransition(boolean enable) {
        if (!explicitRefreshRateHints()) {
            if (enable) {
                getPendingTransaction().setEarlyWakeupStart();
            } else {
                getPendingTransaction().setEarlyWakeupEnd();
            }
            return;
        }
        if (enable) {
            if (mTransitionPrefSession == null) {
                mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession(
                        SystemPerformanceHinter.HINT_SF, mDisplayId, "Transition");
            }
            mTransitionPrefSession.start();
        } else if (mTransitionPrefSession != null) {
            mTransitionPrefSession.close();
        }
    }

    void enableHighFrameRate(boolean enable) {
        if (!explicitRefreshRateHints()) {
            // Done by RefreshRatePolicy.
            return;
        }
        if (enable) {
            if (mHighFrameRateSession == null) {
                mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession(
                        SystemPerformanceHinter.HINT_SF_FRAME_RATE, mDisplayId, "WindowAnimation");
            }
            mHighFrameRateSession.start();
        } else if (mHighFrameRateSession != null) {
            mHighFrameRateSession.close();
        }
    }

    void rotateBounds(@Rotation int oldRotation, @Rotation int newRotation, Rect inOutBounds) {
        // Get display bounds on oldRotation as parent bounds for the rotation.
        getBounds(mTmpRect, oldRotation);
+7 −9
Original line number Diff line number Diff line
@@ -249,7 +249,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {

    private boolean mIsSeamlessRotation = false;
    private IContainerFreezer mContainerFreezer = null;
    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();

    /**
     * {@code true} if some other operation may have caused the originally-recorded state (in
@@ -628,11 +627,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {

        mLogger.mStartTimeNs = SystemClock.elapsedRealtimeNanos();

        mController.updateAnimatingState(mTmpTransaction);
        // merge into the next-time the global transaction is applied. This is too-early to set
        // early-wake anyways, so we don't need to apply immediately (in fact applying right now
        // can preempt more-important work).
        SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
        mController.updateAnimatingState();
    }

    /**
@@ -704,7 +699,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        if (dc == null || mTargetDisplays.contains(dc)) return;
        mTargetDisplays.add(dc);
        addOnTopTasks(dc, mOnTopTasksStart);
        mController.startPerfHintForDisplay(dc.mDisplayId);
        // Handle the case {transition.start(); applyTransaction(wct);} that the animating state
        // is set before collecting participants.
        if (mController.isAnimating()) {
            dc.enableHighPerfTransition(true);
        }
    }

    /**
@@ -1407,8 +1406,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
                    false /* forceRelayout */);
        }
        cleanUpInternal();
        mController.updateAnimatingState(mTmpTransaction);
        mTmpTransaction.apply();
        mController.updateAnimatingState();

        // Handle back animation if it's already started.
        mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
+16 −42
Original line number Diff line number Diff line
@@ -22,10 +22,8 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.SystemPerformanceHinter.HINT_SF;

import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.window.flags.Flags.explicitRefreshRateHints;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -41,18 +39,14 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
import android.window.SystemPerformanceHinter;
import android.window.SystemPerformanceHinter.HighPerfSession;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
@@ -131,7 +125,6 @@ class TransitionController {
    SnapshotController mSnapshotController;
    TransitionTracer mTransitionTracer;

    private SystemPerformanceHinter mSystemPerformanceHinter;
    private boolean mFullReadyTracking = false;

    private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
@@ -185,24 +178,6 @@ class TransitionController {

    private final IBinder.DeathRecipient mTransitionPlayerDeath;

    /**
     * Tracks active perf sessions that boost frame rate and hint sf to increase its
     * estimated work duration.
     */
    private final ArraySet<HighPerfSession> mHighPerfSessions = new ArraySet<>();


    /**
     * Starts a perf hint session which will boost the refresh rate for the display and change
     * sf duration to handle larger workloads.
     */
    void startPerfHintForDisplay(int displayId) {
        if (explicitRefreshRateHints()) {
            mHighPerfSessions.add(mSystemPerformanceHinter.startSession(HINT_SF, displayId,
                    "Transition collected"));
        }
    }

    static class QueuedTransition {
        final Transition mTransition;
        final OnStartCollect mOnStartCollect;
@@ -282,15 +257,9 @@ class TransitionController {
        mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled;
        registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
        setSyncEngine(wms.mSyncEngine);
        setSystemPerformanceHinter(wms.mSystemPerformanceHinter);
        mFullReadyTracking = Flags.transitReadyTracking();
    }

    @VisibleForTesting
    void setSystemPerformanceHinter(SystemPerformanceHinter hinter) {
        mSystemPerformanceHinter = hinter;
    }

    @VisibleForTesting
    void setSyncEngine(BLASTSyncEngine syncEngine) {
        mSyncEngine = syncEngine;
@@ -508,7 +477,7 @@ class TransitionController {
        return false;
    }

    /** @return {@code true} if wc is in a participant subtree */
    /** Returns {@code true} if the display contains a running or pending transition. */
    boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
        if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
            return true;
@@ -1240,12 +1209,22 @@ class TransitionController {
        }
    }

    void updateAnimatingState(SurfaceControl.Transaction t) {
    /** Returns {@code true} if a transition is playing or the collecting transition is started. */
    boolean isAnimating() {
        return mAnimatingState;
    }

    void updateAnimatingState() {
        final boolean animatingState = !mPlayingTransitions.isEmpty()
                    || (mCollectingTransition != null && mCollectingTransition.isStarted());
        if (animatingState && !mAnimatingState) {
            if (!explicitRefreshRateHints()) {
                t.setEarlyWakeupStart();
            // Note that Transition#start() can be called before adding participants, so the
            // enableHighPerfTransition(true) is also called in Transition#recordDisplay.
            for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
                final DisplayContent dc = mAtm.mRootWindowContainer.getChildAt(i);
                if (isTransitionOnDisplay(dc)) {
                    dc.enableHighPerfTransition(true);
                }
            }
            // Usually transitions put quite a load onto the system already (with all the things
            // happening in app), so pause task snapshot persisting to not increase the load.
@@ -1253,18 +1232,13 @@ class TransitionController {
            mAnimatingState = true;
            Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
        } else if (!animatingState && mAnimatingState) {
            if (!explicitRefreshRateHints()) {
                t.setEarlyWakeupEnd();
            for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
                mAtm.mRootWindowContainer.getChildAt(i).enableHighPerfTransition(false);
            }
            mAtm.mWindowManager.scheduleAnimationLocked();
            mSnapshotController.setPause(false);
            mAnimatingState = false;
            Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
            // We close all perf sessions here when all transitions finish. The sessions are created
            // when we collect transitions because we have access to the display id.
            for (HighPerfSession perfSession : mHighPerfSessions) {
                perfSession.close();
            }
        }
    }

+32 −15
Original line number Diff line number Diff line
@@ -117,6 +117,9 @@ public class WindowAnimator {
        scheduleAnimation();

        final RootWindowContainer root = mService.mRoot;
        final boolean useShellTransition = root.mTransitionController.isShellTransitionsEnabled();
        final int animationFlags = useShellTransition ? CHILDREN : (TRANSITION | CHILDREN);
        boolean rootAnimating = false;
        mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
        mBulkUpdateParams = 0;
        root.mOrientationChangeComplete = true;
@@ -149,6 +152,17 @@ public class WindowAnimator {
                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
                            mTransaction);
                }

                if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
                    rootAnimating = true;
                    if (!dc.mLastContainsRunningSurfaceAnimator) {
                        dc.mLastContainsRunningSurfaceAnimator = true;
                        dc.enableHighFrameRate(true);
                    }
                } else if (dc.mLastContainsRunningSurfaceAnimator) {
                    dc.mLastContainsRunningSurfaceAnimator = false;
                    dc.enableHighFrameRate(false);
                }
            }

            cancelAnimation();
@@ -168,8 +182,6 @@ public class WindowAnimator {
            mService.mWindowPlacerLocked.requestTraversal();
        }

        final boolean rootAnimating = root.isAnimating(TRANSITION | CHILDREN /* flags */,
                ANIMATION_TYPE_ALL /* typesToCheck */);
        if (rootAnimating && !mLastRootAnimating) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
        }
@@ -179,20 +191,10 @@ public class WindowAnimator {
        }
        mLastRootAnimating = rootAnimating;

        final boolean runningExpensiveAnimations =
                root.isAnimating(TRANSITION | CHILDREN /* flags */,
                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
                                | ANIMATION_TYPE_RECENTS /* typesToCheck */);
        if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
            // Usually app transitions put quite a load onto the system already (with all the things
            // happening in app), so pause snapshot persisting to not increase the load.
            mService.mSnapshotController.setPause(true);
            mTransaction.setEarlyWakeupStart();
        } else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) {
            mService.mSnapshotController.setPause(false);
            mTransaction.setEarlyWakeupEnd();
        // APP_TRANSITION, SCREEN_ROTATION, TYPE_RECENTS are handled by shell transition.
        if (!useShellTransition) {
            updateRunningExpensiveAnimationsLegacy();
        }
        mRunningExpensiveAnimations = runningExpensiveAnimations;

        SurfaceControl.mergeToGlobalTransaction(mTransaction);
        mService.closeSurfaceTransaction("WindowAnimator");
@@ -208,6 +210,21 @@ public class WindowAnimator {
        }
    }

    private void updateRunningExpensiveAnimationsLegacy() {
        final boolean runningExpensiveAnimations =
                mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */,
                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
                                | ANIMATION_TYPE_RECENTS /* typesToCheck */);
        if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
            mService.mSnapshotController.setPause(true);
            mTransaction.setEarlyWakeupStart();
        } else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) {
            mService.mSnapshotController.setPause(false);
            mTransaction.setEarlyWakeupEnd();
        }
        mRunningExpensiveAnimations = runningExpensiveAnimations;
    }

    private static String bulkUpdateParamsToString(int bulkUpdateParams) {
        StringBuilder builder = new StringBuilder(128);
        if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) {
Loading