3.x: Add missing throwIfFatal calls (#6801)

<|original_code|>
/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.reactivex.rxjava3.internal.schedulers;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.internal.functions.Functions;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;

/**
 * Wrapper for a regular task that gets immediately rescheduled when the task completed.
 */
final class InstantPeriodicTask implements Callable<Void>, Disposable {

    final Runnable task;

    final AtomicReference<Future<?>> rest;

    final AtomicReference<Future<?>> first;

    final ExecutorService executor;

    Thread runner;

    static final FutureTask<Void> CANCELLED = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null);

    InstantPeriodicTask(Runnable task, ExecutorService executor) {
        super();
        this.task = task;
        this.first = new AtomicReference<Future<?>>();
        this.rest = new AtomicReference<Future<?>>();
        this.executor = executor;
    }

    @Override
    public Void call() throws Exception {
        runner = Thread.currentThread();
        try {
            task.run();
            setRest(executor.submit(this));
            runner = null;
        } catch (Throwable ex) {
            runner = null;
            RxJavaPlugins.onError(ex);
        }
        return null;
    }

    @Override
    public void dispose() {
        Future<?> current = first.getAndSet(CANCELLED);
        if (current != null && current != CANCELLED) {
            current.cancel(runner != Thread.currentThread());
        }
        current = rest.getAndSet(CANCELLED);
        if (current != null && current != CANCELLED) {
            current.cancel(runner != Thread.currentThread());
        }
    }

    @Override
    public boolean isDisposed() {
        return first.get() == CANCELLED;
    }

    void setFirst(Future<?> f) {
<|edits_diff|>
--- src/main/java/io/reactivex/rxjava3/internal/schedulers/InstantPeriodicTask.java
+++ src/main/java/io/reactivex/rxjava3/internal/schedulers/InstantPeriodicTask.java
@@ -20,6 +20,7 @@
 import java.util.concurrent.atomic.AtomicReference;

 import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
 import io.reactivex.rxjava3.internal.functions.Functions;
 import io.reactivex.rxjava3.plugins.RxJavaPlugins;

<|current_version|>
/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.reactivex.rxjava3.internal.schedulers;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.Exceptions;
import io.reactivex.rxjava3.internal.functions.Functions;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;

/**
 * Wrapper for a regular task that gets immediately rescheduled when the task completed.
 */
final class InstantPeriodicTask implements Callable<Void>, Disposable {

    final Runnable task;

    final AtomicReference<Future<?>> rest;

    final AtomicReference<Future<?>> first;

    final ExecutorService executor;

    Thread runner;

    static final FutureTask<Void> CANCELLED = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null);

    InstantPeriodicTask(Runnable task, ExecutorService executor) {
        super();
        this.task = task;
        this.first = new AtomicReference<Future<?>>();
        this.rest = new AtomicReference<Future<?>>();
        this.executor = executor;
    }

    @Override
    public Void call() throws Exception {
        runner = Thread.currentThread();
        try {
            task.run();
            setRest(executor.submit(this));
            runner = null;
        } catch (Throwable ex) {
            runner = null;
            RxJavaPlugins.onError(ex);
        }
        return null;
    }

    @Override
    public void dispose() {
        Future<?> current = first.getAndSet(CANCELLED);
        if (current != null && current != CANCELLED) {
            current.cancel(runner != Thread.currentThread());
        }
        current = rest.getAndSet(CANCELLED);
        if (current != null && current != CANCELLED) {
            current.cancel(runner != Thread.currentThread());
        }
    }

    @Override
    public boolean isDisposed() {
        return first.get() == CANCELLED;
    }

    void setFirst(Future<?> f) {
<|next_version|>
/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.reactivex.rxjava3.internal.schedulers;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.Exceptions;
import io.reactivex.rxjava3.internal.functions.Functions;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;

/**
 * Wrapper for a regular task that gets immediately rescheduled when the task completed.
 */
final class InstantPeriodicTask implements Callable<Void>, Disposable {

    final Runnable task;

    final AtomicReference<Future<?>> rest;

    final AtomicReference<Future<?>> first;

    final ExecutorService executor;

    Thread runner;

    static final FutureTask<Void> CANCELLED = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null);

    InstantPeriodicTask(Runnable task, ExecutorService executor) {
        super();
        this.task = task;
        this.first = new AtomicReference<Future<?>>();
        this.rest = new AtomicReference<Future<?>>();
        this.executor = executor;
    }

    @Override
    public Void call() throws Exception {
        runner = Thread.currentThread();
        try {
            task.run();
            setRest(executor.submit(this));
            runner = null;
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            runner = null;
            RxJavaPlugins.onError(ex);
        }
        return null;
    }

    @Override
    public void dispose() {
        Future<?> current = first.getAndSet(CANCELLED);
        if (current != null && current != CANCELLED) {
            current.cancel(runner != Thread.currentThread());
        }
        current = rest.getAndSet(CANCELLED);
        if (current != null && current != CANCELLED) {
            current.cancel(runner != Thread.currentThread());
        }
    }

    @Override
    public boolean isDisposed() {
        return first.get() == CANCELLED;
    }

    void setFirst(Future<?> f) {


3.x: Fix truncation bugs in replay() and ReplaySubject/Processor (#6582)

<|original_code|>

        SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) {
            this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize");
            this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge");
            this.unit = ObjectHelper.requireNonNull(unit, "unit is null");
            this.scheduler = ObjectHelper.requireNonNull(scheduler, "scheduler is null");
            TimedNode<T> h = new TimedNode<T>(null, 0L);
            this.tail = h;
            this.head = h;
        }

        void trim() {
            if (size > maxSize) {
                size--;
                TimedNode<T> h = head;
                head = h.get();
            }
            long limit = scheduler.now(unit) - maxAge;

            TimedNode<T> h = head;

            for (;;) {
                TimedNode<T> next = h.get();
                if (next == null) {
                    head = h;
                    break;
                }

                if (next.time > limit) {
                    head = h;
                    break;
                }

                h = next;
            }

        }

        void trimFinal() {
            long limit = scheduler.now(unit) - maxAge;

            TimedNode<T> h = head;

            for (;;) {
                TimedNode<T> next = h.get();
                if (next == null) {
                    if (h.value != null) {
                        head = new TimedNode<T>(null, 0L);
                    } else {
                        head = h;
                    }
                    break;
                }

                if (next.time > limit) {
                    if (h.value != null) {
                        TimedNode<T> n = new TimedNode<T>(null, 0L);
                        n.lazySet(h.get());
<|edits_diff|>
--- src/main/java/io/reactivex/processors/ReplayProcessor.java
+++ src/main/java/io/reactivex/processors/ReplayProcessor.java
@@ -20,6 +20,10 @@
             TimedNode<T> h = head;

             for (;;) {
+                if (size <= 1) {
+                    head = h;
+                    break;
+                }
                 TimedNode<T> next = h.get();
                 if (next == null) {
                     head = h;
<|current_version|>

        SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) {
            this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize");
            this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge");
            this.unit = ObjectHelper.requireNonNull(unit, "unit is null");
            this.scheduler = ObjectHelper.requireNonNull(scheduler, "scheduler is null");
            TimedNode<T> h = new TimedNode<T>(null, 0L);
            this.tail = h;
            this.head = h;
        }

        void trim() {
            if (size > maxSize) {
                size--;
                TimedNode<T> h = head;
                head = h.get();
            }
            long limit = scheduler.now(unit) - maxAge;

            TimedNode<T> h = head;

            for (;;) {
                if (size <= 1) {
                    head = h;
                    break;
                }
                TimedNode<T> next = h.get();
                if (next == null) {
                    head = h;
                    break;
                }

                if (next.time > limit) {
                    head = h;
                    break;
                }

                h = next;
            }

        }

        void trimFinal() {
            long limit = scheduler.now(unit) - maxAge;

            TimedNode<T> h = head;

            for (;;) {
                TimedNode<T> next = h.get();
                if (next == null) {
                    if (h.value != null) {
                        head = new TimedNode<T>(null, 0L);
                    } else {
                        head = h;
                    }
                    break;
                }

                if (next.time > limit) {
                    if (h.value != null) {
                        TimedNode<T> n = new TimedNode<T>(null, 0L);
                        n.lazySet(h.get());
<|next_version|>

        SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) {
            this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize");
            this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge");
            this.unit = ObjectHelper.requireNonNull(unit, "unit is null");
            this.scheduler = ObjectHelper.requireNonNull(scheduler, "scheduler is null");
            TimedNode<T> h = new TimedNode<T>(null, 0L);
            this.tail = h;
            this.head = h;
        }

        void trim() {
            if (size > maxSize) {
                size--;
                TimedNode<T> h = head;
                head = h.get();
            }
            long limit = scheduler.now(unit) - maxAge;

            TimedNode<T> h = head;

            for (;;) {
                if (size <= 1) {
                    head = h;
                    break;
                }
                TimedNode<T> next = h.get();
                if (next == null) {
                    head = h;
                    break;
                }

                if (next.time > limit) {
                    head = h;
                    break;
                }

                h = next;
                size--;
            }

        }

        void trimFinal() {
            long limit = scheduler.now(unit) - maxAge;

            TimedNode<T> h = head;

            for (;;) {
                TimedNode<T> next = h.get();
                if (next == null) {
                    if (h.value != null) {
                        head = new TimedNode<T>(null, 0L);
                    } else {
                        head = h;
                    }
                    break;
                }

                if (next.time > limit) {
                    if (h.value != null) {
                        TimedNode<T> n = new TimedNode<T>(null, 0L);
                        n.lazySet(h.get());


2.x: fix Flowable.flatMapMaybe/Single maxConcurrency not requesting more (#5287)

<|original_code|>
            drainLoop();
        }

        SpscLinkedArrayQueue<R> getOrCreateQueue() {
            for (;;) {
                SpscLinkedArrayQueue<R> current = queue.get();
                if (current != null) {
                    return current;
                }
                current = new SpscLinkedArrayQueue<R>(Flowable.bufferSize());
                if (queue.compareAndSet(null, current)) {
                    return current;
                }
            }
        }

        void innerError(InnerObserver inner, Throwable e) {
            set.delete(inner);
            if (errors.addThrowable(e)) {
                if (!delayErrors) {
                    s.cancel();
                    set.dispose();
                }
                active.decrementAndGet();
                drain();
            } else {
                RxJavaPlugins.onError(e);
            }
        }

        void innerComplete(InnerObserver inner) {
            set.delete(inner);

            if (get() == 0 && compareAndSet(0, 1)) {
                boolean d = active.decrementAndGet() == 0;
                SpscLinkedArrayQueue<R> q = queue.get();

                if (d && (q == null || q.isEmpty())) {
                    Throwable ex = errors.terminate();
                    if (ex != null) {
                        actual.onError(ex);
                    } else {
                        actual.onComplete();
                    }
                    return;
                }
                if (decrementAndGet() == 0) {
                    return;
                }
                drainLoop();
            } else {
                active.decrementAndGet();
                drain();
            }
        }

        void drain() {
            if (getAndIncrement() == 0) {
                drainLoop();
            }
        }

        void clear() {
            SpscLinkedArrayQueue<R> q = queue.get();
            if (q != null) {
                q.clear();
            }
        }

        void drainLoop() {
            int missed = 1;
            Subscriber<? super R> a = actual;
            AtomicInteger n = active;
            AtomicReference<SpscLinkedArrayQueue<R>> qr = queue;

            for (;;) {
<|edits_diff|>
--- src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java
+++ src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java
@@ -20,6 +20,10 @@
                 if (!delayErrors) {
                     s.cancel();
                     set.dispose();
+                } else {
+                    if (maxConcurrency != Integer.MAX_VALUE) {
+                        s.request(1);
+                    }
                 }
                 active.decrementAndGet();
                 drain();
@@ -43,6 +47,10 @@
                         actual.onComplete();
                     }
                     return;
+                }
+
+                if (maxConcurrency != Integer.MAX_VALUE) {
+                    s.request(1);
                 }
                 if (decrementAndGet() == 0) {
                     return;
<|current_version|>
            drainLoop();
        }

        SpscLinkedArrayQueue<R> getOrCreateQueue() {
            for (;;) {
                SpscLinkedArrayQueue<R> current = queue.get();
                if (current != null) {
                    return current;
                }
                current = new SpscLinkedArrayQueue<R>(Flowable.bufferSize());
                if (queue.compareAndSet(null, current)) {
                    return current;
                }
            }
        }

        void innerError(InnerObserver inner, Throwable e) {
            set.delete(inner);
            if (errors.addThrowable(e)) {
                if (!delayErrors) {
                    s.cancel();
                    set.dispose();
                } else {
                    if (maxConcurrency != Integer.MAX_VALUE) {
                        s.request(1);
                    }
                }
                active.decrementAndGet();
                drain();
            } else {
                RxJavaPlugins.onError(e);
            }
        }

        void innerComplete(InnerObserver inner) {
            set.delete(inner);

            if (get() == 0 && compareAndSet(0, 1)) {
                boolean d = active.decrementAndGet() == 0;
                SpscLinkedArrayQueue<R> q = queue.get();

                if (d && (q == null || q.isEmpty())) {
                    Throwable ex = errors.terminate();
                    if (ex != null) {
                        actual.onError(ex);
                    } else {
                        actual.onComplete();
                    }
                    return;
                }

                if (maxConcurrency != Integer.MAX_VALUE) {
                    s.request(1);
                }
                if (decrementAndGet() == 0) {
                    return;
                }
                drainLoop();
            } else {
                active.decrementAndGet();
                drain();
            }
        }

        void drain() {
            if (getAndIncrement() == 0) {
                drainLoop();
            }
        }

        void clear() {
            SpscLinkedArrayQueue<R> q = queue.get();
            if (q != null) {
                q.clear();
            }
        }

        void drainLoop() {
            int missed = 1;
            Subscriber<? super R> a = actual;
            AtomicInteger n = active;
            AtomicReference<SpscLinkedArrayQueue<R>> qr = queue;

            for (;;) {
<|next_version|>
            drainLoop();
        }

        SpscLinkedArrayQueue<R> getOrCreateQueue() {
            for (;;) {
                SpscLinkedArrayQueue<R> current = queue.get();
                if (current != null) {
                    return current;
                }
                current = new SpscLinkedArrayQueue<R>(Flowable.bufferSize());
                if (queue.compareAndSet(null, current)) {
                    return current;
                }
            }
        }

        void innerError(InnerObserver inner, Throwable e) {
            set.delete(inner);
            if (errors.addThrowable(e)) {
                if (!delayErrors) {
                    s.cancel();
                    set.dispose();
                } else {
                    if (maxConcurrency != Integer.MAX_VALUE) {
                        s.request(1);
                    }
                }
                active.decrementAndGet();
                drain();
            } else {
                RxJavaPlugins.onError(e);
            }
        }

        void innerComplete(InnerObserver inner) {
            set.delete(inner);

            if (get() == 0 && compareAndSet(0, 1)) {
                boolean d = active.decrementAndGet() == 0;
                SpscLinkedArrayQueue<R> q = queue.get();

                if (d && (q == null || q.isEmpty())) {
                    Throwable ex = errors.terminate();
                    if (ex != null) {
                        actual.onError(ex);
                    } else {
                        actual.onComplete();
                    }
                    return;
                }

                if (maxConcurrency != Integer.MAX_VALUE) {
                    s.request(1);
                }
                if (decrementAndGet() == 0) {
                    return;
                }
                drainLoop();
            } else {
                active.decrementAndGet();
                if (maxConcurrency != Integer.MAX_VALUE) {
                    s.request(1);
                }
                drain();
            }
        }

        void drain() {
            if (getAndIncrement() == 0) {
                drainLoop();
            }
        }

        void clear() {
            SpscLinkedArrayQueue<R> q = queue.get();
            if (q != null) {
                q.clear();
            }
        }

        void drainLoop() {
            int missed = 1;
            Subscriber<? super R> a = actual;
            AtomicInteger n = active;
            AtomicReference<SpscLinkedArrayQueue<R>> qr = queue;

            for (;;) {


FlowableScanSeed - prevent post-terminal events (#4899)

<|original_code|>
        this.seedSupplier = seedSupplier;
    }

    @Override
    protected void subscribeActual(Subscriber<? super R> s) {
        R r;

        try {
            r = ObjectHelper.requireNonNull(seedSupplier.call(), "The seed supplied is null");
        } catch (Throwable e) {
            Exceptions.throwIfFatal(e);
            EmptySubscription.error(e, s);
            return;
        }

        source.subscribe(new ScanSeedSubscriber<T, R>(s, accumulator, r));
    }

    static final class ScanSeedSubscriber<T, R> extends SinglePostCompleteSubscriber<T, R> {
        private static final long serialVersionUID = -1776795561228106469L;

        final BiFunction<R, ? super T, R> accumulator;

        ScanSeedSubscriber(Subscriber<? super R> actual, BiFunction<R, ? super T, R> accumulator, R value) {
            super(actual);
            this.accumulator = accumulator;
            this.value = value;
        }

        @Override
        public void onNext(T t) {
            R v = value;

            R u;

            try {
                u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value");
            } catch (Throwable e) {
                Exceptions.throwIfFatal(e);
                s.cancel();
                onError(e);
                return;
            }

            value = u;
            produced++;
            actual.onNext(v);
        }

        @Override
        public void onError(Throwable t) {
            value = null;
            actual.onError(t);
        }

        @Override
        public void onComplete() {
            complete(value);
        }
    }
}

<|edits_diff|>
--- src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java
+++ src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java
@@ -20,6 +20,8 @@
         private static final long serialVersionUID = -1776795561228106469L;

         final BiFunction<R, ? super T, R> accumulator;
+
+        boolean done;

         ScanSeedSubscriber(Subscriber<? super R> actual, BiFunction<R, ? super T, R> accumulator, R value) {
             super(actual);
@@ -29,6 +31,10 @@

         @Override
         public void onNext(T t) {
+            if (done) {
+                return;
+            }
+
             R v = value;

             R u;
@@ -49,6 +55,10 @@

         @Override
         public void onError(Throwable t) {
+            if (done) {
+                return;
+            }
+            done = true;
             value = null;
             actual.onError(t);
         }
<|current_version|>
        this.seedSupplier = seedSupplier;
    }

    @Override
    protected void subscribeActual(Subscriber<? super R> s) {
        R r;

        try {
            r = ObjectHelper.requireNonNull(seedSupplier.call(), "The seed supplied is null");
        } catch (Throwable e) {
            Exceptions.throwIfFatal(e);
            EmptySubscription.error(e, s);
            return;
        }

        source.subscribe(new ScanSeedSubscriber<T, R>(s, accumulator, r));
    }

    static final class ScanSeedSubscriber<T, R> extends SinglePostCompleteSubscriber<T, R> {
        private static final long serialVersionUID = -1776795561228106469L;

        final BiFunction<R, ? super T, R> accumulator;

        boolean done;

        ScanSeedSubscriber(Subscriber<? super R> actual, BiFunction<R, ? super T, R> accumulator, R value) {
            super(actual);
            this.accumulator = accumulator;
            this.value = value;
        }

        @Override
        public void onNext(T t) {
            if (done) {
                return;
            }

            R v = value;

            R u;

            try {
                u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value");
            } catch (Throwable e) {
                Exceptions.throwIfFatal(e);
                s.cancel();
                onError(e);
                return;
            }

            value = u;
            produced++;
            actual.onNext(v);
        }

        @Override
        public void onError(Throwable t) {
            if (done) {
                return;
            }
            done = true;
            value = null;
            actual.onError(t);
        }

        @Override
        public void onComplete() {
            complete(value);
        }
    }
}

<|next_version|>
        this.seedSupplier = seedSupplier;
    }

    @Override
    protected void subscribeActual(Subscriber<? super R> s) {
        R r;

        try {
            r = ObjectHelper.requireNonNull(seedSupplier.call(), "The seed supplied is null");
        } catch (Throwable e) {
            Exceptions.throwIfFatal(e);
            EmptySubscription.error(e, s);
            return;
        }

        source.subscribe(new ScanSeedSubscriber<T, R>(s, accumulator, r));
    }

    static final class ScanSeedSubscriber<T, R> extends SinglePostCompleteSubscriber<T, R> {
        private static final long serialVersionUID = -1776795561228106469L;

        final BiFunction<R, ? super T, R> accumulator;

        boolean done;

        ScanSeedSubscriber(Subscriber<? super R> actual, BiFunction<R, ? super T, R> accumulator, R value) {
            super(actual);
            this.accumulator = accumulator;
            this.value = value;
        }

        @Override
        public void onNext(T t) {
            if (done) {
                return;
            }

            R v = value;

            R u;

            try {
                u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value");
            } catch (Throwable e) {
                Exceptions.throwIfFatal(e);
                s.cancel();
                onError(e);
                return;
            }

            value = u;
            produced++;
            actual.onNext(v);
        }

        @Override
        public void onError(Throwable t) {
            if (done) {
                return;
            }
            done = true;
            value = null;
            actual.onError(t);
        }

        @Override
        public void onComplete() {
            if (done) {
                return;
            }
            done = true;
            complete(value);
        }
    }
}


2.x: add system properties to adjust thread priorities of Schedulers (#4503)

<|original_code|>
/**
 * Copyright 2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.internal.schedulers;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

public final class RxThreadFactory extends AtomicLong implements ThreadFactory {
    /** */
    private static final long serialVersionUID = -7789753024099756196L;

    final String prefix;

    static volatile boolean CREATE_TRACE;

    public RxThreadFactory(String prefix) {
        this.prefix = prefix;
    }

    @Override
    public Thread newThread(Runnable r) {
        StringBuilder nameBuilder = new StringBuilder(prefix).append('-').append(incrementAndGet());

        if (CREATE_TRACE) {
            nameBuilder.append("\r\n");
            for (StackTraceElement se :Thread.currentThread().getStackTrace()) {
                String s = se.toString();
                if (s.contains("sun.reflect.")) {
                    continue;
                }
                if (s.contains("junit.runners.")) {
                    continue;
                }
                if (s.contains("org.gradle.internal.")) {
                    continue;
                }
                if (s.contains("java.util.concurrent.ThreadPoolExecutor")) {
                    continue;
                }
                nameBuilder.append(s).append("\r\n");
            }
        }
        Thread t = new Thread(r, nameBuilder.toString());
        t.setDaemon(true);
        return t;
    }

    @Override
    public String toString() {
        return "RxThreadFactory[" + prefix + "]";
    }
}

<|edits_diff|>
--- src/main/java/io/reactivex/internal/schedulers/RxThreadFactory.java
+++ src/main/java/io/reactivex/internal/schedulers/RxThreadFactory.java
@@ -16,16 +16,27 @@
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicLong;

+/**
+ * A ThreadFactory that counts how many threads have been created and given a prefix,
+ * sets the created Thread's name to {@code prefix-count}.
+ */
 public final class RxThreadFactory extends AtomicLong implements ThreadFactory {
     /** */
     private static final long serialVersionUID = -7789753024099756196L;

     final String prefix;

+    final int priority;
+
     static volatile boolean CREATE_TRACE;

     public RxThreadFactory(String prefix) {
+        this(prefix, Thread.NORM_PRIORITY);
+    }
+
+    public RxThreadFactory(String prefix, int priority) {
         this.prefix = prefix;
+        this.priority = priority;
     }

     @Override
<|current_version|>
/**
 * Copyright 2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.internal.schedulers;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A ThreadFactory that counts how many threads have been created and given a prefix,
 * sets the created Thread's name to {@code prefix-count}.
 */
public final class RxThreadFactory extends AtomicLong implements ThreadFactory {
    /** */
    private static final long serialVersionUID = -7789753024099756196L;

    final String prefix;

    final int priority;

    static volatile boolean CREATE_TRACE;

    public RxThreadFactory(String prefix) {
        this(prefix, Thread.NORM_PRIORITY);
    }

    public RxThreadFactory(String prefix, int priority) {
        this.prefix = prefix;
        this.priority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        StringBuilder nameBuilder = new StringBuilder(prefix).append('-').append(incrementAndGet());

        if (CREATE_TRACE) {
            nameBuilder.append("\r\n");
            for (StackTraceElement se :Thread.currentThread().getStackTrace()) {
                String s = se.toString();
                if (s.contains("sun.reflect.")) {
                    continue;
                }
                if (s.contains("junit.runners.")) {
                    continue;
                }
                if (s.contains("org.gradle.internal.")) {
                    continue;
                }
                if (s.contains("java.util.concurrent.ThreadPoolExecutor")) {
                    continue;
                }
                nameBuilder.append(s).append("\r\n");
            }
        }
        Thread t = new Thread(r, nameBuilder.toString());
        t.setDaemon(true);
        return t;
    }

    @Override
    public String toString() {
        return "RxThreadFactory[" + prefix + "]";
    }
}

<|next_version|>
/**
 * Copyright 2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.internal.schedulers;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A ThreadFactory that counts how many threads have been created and given a prefix,
 * sets the created Thread's name to {@code prefix-count}.
 */
public final class RxThreadFactory extends AtomicLong implements ThreadFactory {
    /** */
    private static final long serialVersionUID = -7789753024099756196L;

    final String prefix;

    final int priority;

    static volatile boolean CREATE_TRACE;

    public RxThreadFactory(String prefix) {
        this(prefix, Thread.NORM_PRIORITY);
    }

    public RxThreadFactory(String prefix, int priority) {
        this.prefix = prefix;
        this.priority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        StringBuilder nameBuilder = new StringBuilder(prefix).append('-').append(incrementAndGet());

        if (CREATE_TRACE) {
            nameBuilder.append("\r\n");
            for (StackTraceElement se :Thread.currentThread().getStackTrace()) {
                String s = se.toString();
                if (s.contains("sun.reflect.")) {
                    continue;
                }
                if (s.contains("junit.runners.")) {
                    continue;
                }
                if (s.contains("org.gradle.internal.")) {
                    continue;
                }
                if (s.contains("java.util.concurrent.ThreadPoolExecutor")) {
                    continue;
                }
                nameBuilder.append(s).append("\r\n");
            }
        }
        Thread t = new Thread(r, nameBuilder.toString());
        t.setPriority(priority);
        t.setDaemon(true);
        return t;
    }

    @Override
    public String toString() {
        return "RxThreadFactory[" + prefix + "]";
    }
}


2.x: switch to throwing Action, switchMapDelayError (#4357)

<|original_code|>
/**
 * Copyright 2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.disposables;

import java.util.concurrent.Future;

import org.reactivestreams.Subscription;

import io.reactivex.internal.disposables.EmptyDisposable;
import io.reactivex.internal.functions.Functions;

/**
 * Utility class to help create disposables by wrapping
 * other types.
 */
public final class Disposables {
    /** Utility class. */
    private Disposables() {
        throw new IllegalStateException("No instances!");
    }

    public static Disposable from(Runnable run) {
        return new RunnableDisposable(run);
    }

    public static Disposable from(Future<?> future) {
        return from(future, true);
    }

    public static Disposable from(Future<?> future, boolean allowInterrupt) {
        return new FutureDisposable(future, allowInterrupt);
    }

    public static Disposable from(Subscription subscription) {
        return new SubscriptionDisposable(subscription);
    }

    public static Disposable empty() {
        return from(Functions.EMPTY_RUNNABLE);
    }

    public static Disposable disposed() {
        return EmptyDisposable.INSTANCE;
    }
}

<|edits_diff|>
--- src/main/java/io/reactivex/disposables/Disposables.java
+++ src/main/java/io/reactivex/disposables/Disposables.java
@@ -17,6 +17,7 @@

 import org.reactivestreams.Subscription;

+import io.reactivex.functions.Action;
 import io.reactivex.internal.disposables.EmptyDisposable;
 import io.reactivex.internal.functions.Functions;

<|current_version|>
/**
 * Copyright 2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.disposables;

import java.util.concurrent.Future;

import org.reactivestreams.Subscription;

import io.reactivex.functions.Action;
import io.reactivex.internal.disposables.EmptyDisposable;
import io.reactivex.internal.functions.Functions;

/**
 * Utility class to help create disposables by wrapping
 * other types.
 */
public final class Disposables {
    /** Utility class. */
    private Disposables() {
        throw new IllegalStateException("No instances!");
    }

    public static Disposable from(Runnable run) {
        return new RunnableDisposable(run);
    }

    public static Disposable from(Future<?> future) {
        return from(future, true);
    }

    public static Disposable from(Future<?> future, boolean allowInterrupt) {
        return new FutureDisposable(future, allowInterrupt);
    }

    public static Disposable from(Subscription subscription) {
        return new SubscriptionDisposable(subscription);
    }

    public static Disposable empty() {
        return from(Functions.EMPTY_RUNNABLE);
    }

    public static Disposable disposed() {
        return EmptyDisposable.INSTANCE;
    }
}

<|next_version|>
/**
 * Copyright 2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.disposables;

import java.util.concurrent.Future;

import org.reactivestreams.Subscription;

import io.reactivex.functions.Action;
import io.reactivex.internal.disposables.EmptyDisposable;
import io.reactivex.internal.functions.Functions;

/**
 * Utility class to help create disposables by wrapping
 * other types.
 */
public final class Disposables {
    /** Utility class. */
    private Disposables() {
        throw new IllegalStateException("No instances!");
    }

    public static Disposable from(Runnable run) {
        return new RunnableDisposable(run);
    }

    public static Disposable from(Action run) {
        return new ActionDisposable(run);
    }

    public static Disposable from(Future<?> future) {
        return from(future, true);
    }

    public static Disposable from(Future<?> future, boolean allowInterrupt) {
        return new FutureDisposable(future, allowInterrupt);
    }

    public static Disposable from(Subscription subscription) {
        return new SubscriptionDisposable(subscription);
    }

    public static Disposable empty() {
        return from(Functions.EMPTY_RUNNABLE);
    }

    public static Disposable disposed() {
        return EmptyDisposable.INSTANCE;
    }
}


Fixes to MpscLinkedQueue, some minor refactorings.

<|original_code|>
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */
package io.reactivex.internal.util;

import java.io.Serializable;

import org.reactivestreams.*;

/**
 * Lightweight notification handling utility class.
 */
public enum NotificationLite {
    // No instances
    ;

    /**
     * Indicates a completion notification.
     */
    private enum Complete {
        INSTANCE;
    }

    /**
     * Wraps a Throwable.
     */
    private static final class ErrorNotification implements Serializable {
        /** */
        private static final long serialVersionUID = -8759979445933046293L;
        final Throwable e;
        ErrorNotification(Throwable e) {
            this.e = e;
        }
    }

    /**
     * Wraps a Subscription.
     */
    private static final class SubscriptionNotification implements Serializable {
        /** */
        private static final long serialVersionUID = -1322257508628817540L;
        final Subscription s;
        SubscriptionNotification(Subscription s) {
            this.s = s;
        }
    }

    /**
     * Converts a value into a notification value.
     * @param value the value to convert
     * @return the notification representing the value
     */
    public static <T> Object next(T value) {
        return value;
    }

    /**
     * Returns a complete notification.
     * @return a complete notification
     */
    public static Object complete() {
        return Complete.INSTANCE;
    }

    /**
     * Converts a Throwable into a notification value.
     * @param e the Throwable to convert
     * @return the notification representing the Throwable
     */
<|edits_diff|>
--- src/main/java/io/reactivex/internal/util/NotificationLite.java
+++ src/main/java/io/reactivex/internal/util/NotificationLite.java
@@ -20,6 +20,10 @@
      */
     private enum Complete {
         INSTANCE;
+        @Override
+        public String toString() {
+            return "NotificationLite.Complete";
+        };
     }

     /**
@@ -31,6 +35,11 @@
         final Throwable e;
         ErrorNotification(Throwable e) {
             this.e = e;
+        }
+
+        @Override
+        public String toString() {
+            return "NotificationLite.Error[" + e + "]";
         }
     }

<|current_version|>
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */
package io.reactivex.internal.util;

import java.io.Serializable;

import org.reactivestreams.*;

/**
 * Lightweight notification handling utility class.
 */
public enum NotificationLite {
    // No instances
    ;

    /**
     * Indicates a completion notification.
     */
    private enum Complete {
        INSTANCE;
        @Override
        public String toString() {
            return "NotificationLite.Complete";
        };
    }

    /**
     * Wraps a Throwable.
     */
    private static final class ErrorNotification implements Serializable {
        /** */
        private static final long serialVersionUID = -8759979445933046293L;
        final Throwable e;
        ErrorNotification(Throwable e) {
            this.e = e;
        }

        @Override
        public String toString() {
            return "NotificationLite.Error[" + e + "]";
        }
    }

    /**
     * Wraps a Subscription.
     */
    private static final class SubscriptionNotification implements Serializable {
        /** */
        private static final long serialVersionUID = -1322257508628817540L;
        final Subscription s;
        SubscriptionNotification(Subscription s) {
            this.s = s;
        }
    }

    /**
     * Converts a value into a notification value.
     * @param value the value to convert
     * @return the notification representing the value
     */
    public static <T> Object next(T value) {
        return value;
    }

    /**
     * Returns a complete notification.
     * @return a complete notification
     */
    public static Object complete() {
        return Complete.INSTANCE;
    }

    /**
     * Converts a Throwable into a notification value.
     * @param e the Throwable to convert
     * @return the notification representing the Throwable
     */
<|next_version|>
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */
package io.reactivex.internal.util;

import java.io.Serializable;

import org.reactivestreams.*;

/**
 * Lightweight notification handling utility class.
 */
public enum NotificationLite {
    // No instances
    ;

    /**
     * Indicates a completion notification.
     */
    private enum Complete {
        INSTANCE;
        @Override
        public String toString() {
            return "NotificationLite.Complete";
        };
    }

    /**
     * Wraps a Throwable.
     */
    private static final class ErrorNotification implements Serializable {
        /** */
        private static final long serialVersionUID = -8759979445933046293L;
        final Throwable e;
        ErrorNotification(Throwable e) {
            this.e = e;
        }

        @Override
        public String toString() {
            return "NotificationLite.Error[" + e + "]";
        }
    }

    /**
     * Wraps a Subscription.
     */
    private static final class SubscriptionNotification implements Serializable {
        /** */
        private static final long serialVersionUID = -1322257508628817540L;
        final Subscription s;
        SubscriptionNotification(Subscription s) {
            this.s = s;
        }

        @Override
        public String toString() {
            return "NotificationLite.Subscription[" + s + "]";
        }
    }

    /**
     * Converts a value into a notification value.
     * @param value the value to convert
     * @return the notification representing the value
     */
    public static <T> Object next(T value) {
        return value;
    }

    /**
     * Returns a complete notification.
     * @return a complete notification
     */
    public static Object complete() {
        return Complete.INSTANCE;
    }

    /**
     * Converts a Throwable into a notification value.
     * @param e the Throwable to convert
     * @return the notification representing the Throwable
     */


Configure alpha as false explicitly on jpeg encode tests

<|original_code|>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 18)
public class BitmapEncoderTest {
  private EncoderHarness harness;

  @Before
  public void setUp() {
    harness = new EncoderHarness();
  }

  @After
  public void tearDown() {
    harness.tearDown();
  }

  @Test
  public void testBitmapIsEncoded() throws IOException {
    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
  }

  @Test
  public void testBitmapIsEncodedWithGivenQuality() throws IOException {
    int quality = 7;
    harness.setQuality(quality);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, quality));
  }

  @Test
  public void testEncoderObeysNonNullCompressFormat() throws IOException {
    Bitmap.CompressFormat format = Bitmap.CompressFormat.WEBP;
    harness.setFormat(format);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.WEBP, 90));
  }

  @Test
  public void testEncoderEncodesJpegWithNullFormatAndBitmapWithoutAlpha() throws IOException {
    harness.setFormat(null);
    harness.bitmap.setHasAlpha(false);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
  }

  @Test
  public void testEncoderEncodesPngWithNullFormatAndBitmapWithAlpha() throws IOException {
    harness.setFormat(null);
    harness.bitmap.setHasAlpha(true);
<|edits_diff|>
--- library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoderTest.java
+++ library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoderTest.java
@@ -20,6 +20,8 @@

   @Test
   public void testBitmapIsEncoded() throws IOException {
+    harness.bitmap.setHasAlpha(false);
+
     assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
   }

<|current_version|>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 18)
public class BitmapEncoderTest {
  private EncoderHarness harness;

  @Before
  public void setUp() {
    harness = new EncoderHarness();
  }

  @After
  public void tearDown() {
    harness.tearDown();
  }

  @Test
  public void testBitmapIsEncoded() throws IOException {
    harness.bitmap.setHasAlpha(false);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
  }

  @Test
  public void testBitmapIsEncodedWithGivenQuality() throws IOException {
    int quality = 7;
    harness.setQuality(quality);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, quality));
  }

  @Test
  public void testEncoderObeysNonNullCompressFormat() throws IOException {
    Bitmap.CompressFormat format = Bitmap.CompressFormat.WEBP;
    harness.setFormat(format);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.WEBP, 90));
  }

  @Test
  public void testEncoderEncodesJpegWithNullFormatAndBitmapWithoutAlpha() throws IOException {
    harness.setFormat(null);
    harness.bitmap.setHasAlpha(false);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
  }

  @Test
  public void testEncoderEncodesPngWithNullFormatAndBitmapWithAlpha() throws IOException {
    harness.setFormat(null);
    harness.bitmap.setHasAlpha(true);
<|next_version|>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 18)
public class BitmapEncoderTest {
  private EncoderHarness harness;

  @Before
  public void setUp() {
    harness = new EncoderHarness();
  }

  @After
  public void tearDown() {
    harness.tearDown();
  }

  @Test
  public void testBitmapIsEncoded() throws IOException {
    harness.bitmap.setHasAlpha(false);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
  }

  @Test
  public void testBitmapIsEncodedWithGivenQuality() throws IOException {
    int quality = 7;
    harness.setQuality(quality);
    harness.bitmap.setHasAlpha(false);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, quality));
  }

  @Test
  public void testEncoderObeysNonNullCompressFormat() throws IOException {
    Bitmap.CompressFormat format = Bitmap.CompressFormat.WEBP;
    harness.setFormat(format);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.WEBP, 90));
  }

  @Test
  public void testEncoderEncodesJpegWithNullFormatAndBitmapWithoutAlpha() throws IOException {
    harness.setFormat(null);
    harness.bitmap.setHasAlpha(false);

    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));
  }

  @Test
  public void testEncoderEncodesPngWithNullFormatAndBitmapWithAlpha() throws IOException {
    harness.setFormat(null);
    harness.bitmap.setHasAlpha(true);


Add an experimental option to Glide to avoid hardware Bitmaps until after the first frame.

<|original_code|>
  private static final File FD_SIZE_LIST = new File("/proc/self/fd");

  /**
   * Each FD check takes 1-2ms, so to avoid overhead, only check every N decodes. 50 is more or less
   * arbitrary.
   */
  private static final int MINIMUM_DECODES_BETWEEN_FD_CHECKS = 50;

  /**
   * 700 with an error of 50 Bitmaps in between at two FDs each lets us use up to 800 FDs for
   * hardware Bitmaps.
   *
   *
Prior to P, the limit per process was 1024 FDs. In P, the limit was updated to 32k FDs per
   * process.
   *
   *
Access to this variable will be removed in a future version without deprecation.
   */
  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O = 700;
  // 20k.
  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P = 20000;

  private static volatile HardwareConfigState instance;

  private final boolean isHardwareConfigAllowedByDeviceModel;
  private final int fdCountLimit;
  private final int minHardwareDimension;

  @GuardedBy("this")
  private int decodesSinceLastFdCheck;

  @GuardedBy("this")
  private boolean isFdSizeBelowHardwareLimit = true;

  public static HardwareConfigState getInstance() {
    if (instance == null) {
      synchronized (HardwareConfigState.class) {
        if (instance == null) {
          instance = new HardwareConfigState();
        }
      }
    }
    return instance;
  }

  @VisibleForTesting
  HardwareConfigState() {
    isHardwareConfigAllowedByDeviceModel = isHardwareConfigAllowedByDeviceModel();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      fdCountLimit = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P;
      minHardwareDimension = MIN_HARDWARE_DIMENSION_P;
    } else {
      fdCountLimit = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O;
      minHardwareDimension = MIN_HARDWARE_DIMENSION_O;
    }
  }

  public boolean isHardwareConfigAllowed(
      int targetWidth,
      int targetHeight,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    if (!isHardwareConfigAllowed
        || !isHardwareConfigAllowedByDeviceModel
        || Build.VERSION.SDK_INT < Build.VERSION_CODES.O
        || isExifOrientationRequired) {
      return false;
    }

    return targetWidth >= minHardwareDimension
        && targetHeight >= minHardwareDimension
        // Make sure to call isFdSizeBelowHardwareLimit last because it has side affects.
        && isFdSizeBelowHardwareLimit();
  }

  @TargetApi(Build.VERSION_CODES.O)
  boolean setHardwareConfigIfAllowed(
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options optionsWithScaling,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    boolean result =
        isHardwareConfigAllowed(
            targetWidth, targetHeight, isHardwareConfigAllowed, isExifOrientationRequired);
    if (result) {
      optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
      optionsWithScaling.inMutable = false;
    }
<|edits_diff|>
--- library/src/main/java/com/bumptech/glide/load/resource/bitmap/HardwareConfigState.java
+++ library/src/main/java/com/bumptech/glide/load/resource/bitmap/HardwareConfigState.java
@@ -20,6 +20,7 @@
   private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P = 20000;

   private static volatile HardwareConfigState instance;
+  private static volatile boolean waitForFirstFrame;

   private final boolean isHardwareConfigAllowedByDeviceModel;
   private final int fdCountLimit;
@@ -30,6 +31,8 @@

   @GuardedBy("this")
   private boolean isFdSizeBelowHardwareLimit = true;
+
+  private volatile boolean isFirstFrameDrawn;

   public static HardwareConfigState getInstance() {
     if (instance == null) {
<|current_version|>
  private static final File FD_SIZE_LIST = new File("/proc/self/fd");

  /**
   * Each FD check takes 1-2ms, so to avoid overhead, only check every N decodes. 50 is more or less
   * arbitrary.
   */
  private static final int MINIMUM_DECODES_BETWEEN_FD_CHECKS = 50;

  /**
   * 700 with an error of 50 Bitmaps in between at two FDs each lets us use up to 800 FDs for
   * hardware Bitmaps.
   *
   *
Prior to P, the limit per process was 1024 FDs. In P, the limit was updated to 32k FDs per
   * process.
   *
   *
Access to this variable will be removed in a future version without deprecation.
   */
  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O = 700;
  // 20k.
  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P = 20000;

  private static volatile HardwareConfigState instance;
  private static volatile boolean waitForFirstFrame;

  private final boolean isHardwareConfigAllowedByDeviceModel;
  private final int fdCountLimit;
  private final int minHardwareDimension;

  @GuardedBy("this")
  private int decodesSinceLastFdCheck;

  @GuardedBy("this")
  private boolean isFdSizeBelowHardwareLimit = true;

  private volatile boolean isFirstFrameDrawn;

  public static HardwareConfigState getInstance() {
    if (instance == null) {
      synchronized (HardwareConfigState.class) {
        if (instance == null) {
          instance = new HardwareConfigState();
        }
      }
    }
    return instance;
  }

  @VisibleForTesting
  HardwareConfigState() {
    isHardwareConfigAllowedByDeviceModel = isHardwareConfigAllowedByDeviceModel();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      fdCountLimit = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P;
      minHardwareDimension = MIN_HARDWARE_DIMENSION_P;
    } else {
      fdCountLimit = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O;
      minHardwareDimension = MIN_HARDWARE_DIMENSION_O;
    }
  }

  public boolean isHardwareConfigAllowed(
      int targetWidth,
      int targetHeight,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    if (!isHardwareConfigAllowed
        || !isHardwareConfigAllowedByDeviceModel
        || Build.VERSION.SDK_INT < Build.VERSION_CODES.O
        || isExifOrientationRequired) {
      return false;
    }

    return targetWidth >= minHardwareDimension
        && targetHeight >= minHardwareDimension
        // Make sure to call isFdSizeBelowHardwareLimit last because it has side affects.
        && isFdSizeBelowHardwareLimit();
  }

  @TargetApi(Build.VERSION_CODES.O)
  boolean setHardwareConfigIfAllowed(
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options optionsWithScaling,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    boolean result =
        isHardwareConfigAllowed(
            targetWidth, targetHeight, isHardwareConfigAllowed, isExifOrientationRequired);
    if (result) {
      optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
      optionsWithScaling.inMutable = false;
    }
<|next_version|>
  private static final File FD_SIZE_LIST = new File("/proc/self/fd");

  /**
   * Each FD check takes 1-2ms, so to avoid overhead, only check every N decodes. 50 is more or less
   * arbitrary.
   */
  private static final int MINIMUM_DECODES_BETWEEN_FD_CHECKS = 50;

  /**
   * 700 with an error of 50 Bitmaps in between at two FDs each lets us use up to 800 FDs for
   * hardware Bitmaps.
   *
   *
Prior to P, the limit per process was 1024 FDs. In P, the limit was updated to 32k FDs per
   * process.
   *
   *
Access to this variable will be removed in a future version without deprecation.
   */
  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O = 700;
  // 20k.
  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P = 20000;

  private static volatile HardwareConfigState instance;
  private static volatile boolean waitForFirstFrame;

  private final boolean isHardwareConfigAllowedByDeviceModel;
  private final int fdCountLimit;
  private final int minHardwareDimension;

  @GuardedBy("this")
  private int decodesSinceLastFdCheck;

  @GuardedBy("this")
  private boolean isFdSizeBelowHardwareLimit = true;

  private volatile boolean isFirstFrameDrawn;

  public static HardwareConfigState getInstance() {
    if (instance == null) {
      synchronized (HardwareConfigState.class) {
        if (instance == null) {
          instance = new HardwareConfigState();
        }
      }
    }
    return instance;
  }

  @VisibleForTesting
  HardwareConfigState() {
    isHardwareConfigAllowedByDeviceModel = isHardwareConfigAllowedByDeviceModel();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      fdCountLimit = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P;
      minHardwareDimension = MIN_HARDWARE_DIMENSION_P;
    } else {
      fdCountLimit = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O;
      minHardwareDimension = MIN_HARDWARE_DIMENSION_O;
    }
  }

  public boolean isHardwareConfigAllowed(
      int targetWidth,
      int targetHeight,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    if (!isHardwareConfigAllowed
        || !isHardwareConfigAllowedByDeviceModel
        || Build.VERSION.SDK_INT < Build.VERSION_CODES.O
        || (waitForFirstFrame && !isFirstFrameDrawn)
        || isExifOrientationRequired) {
      return false;
    }

    return targetWidth >= minHardwareDimension
        && targetHeight >= minHardwareDimension
        // Make sure to call isFdSizeBelowHardwareLimit last because it has side affects.
        && isFdSizeBelowHardwareLimit();
  }

  @TargetApi(Build.VERSION_CODES.O)
  boolean setHardwareConfigIfAllowed(
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options optionsWithScaling,
      boolean isHardwareConfigAllowed,
      boolean isExifOrientationRequired) {
    boolean result =
        isHardwareConfigAllowed(
            targetWidth, targetHeight, isHardwareConfigAllowed, isExifOrientationRequired);
    if (result) {
      optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
      optionsWithScaling.inMutable = false;
    }


Annotate tests to use Robolectric's LEGACY LooperMode.

<|original_code|>
package com.bumptech.glide.load.resource.gif;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.gifdecoder.GifHeaderParser;
import com.bumptech.glide.load.ImageHeaderParser;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;
import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;
import com.bumptech.glide.tests.GlideShadowLooper;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 18, shadows = GlideShadowLooper.class)
public class ByteBufferGifDecoderTest {
  private static final byte[] GIF_HEADER = new byte[] {0x47, 0x49, 0x46};
  private static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;

  private ByteBufferGifDecoder decoder;
  private GifHeader gifHeader;
  private Options options;

  @Mock private BitmapPool bitmapPool;
  @Mock private GifHeaderParser parser;
  @Mock private GifDecoder gifDecoder;
  @Mock private ByteBufferGifDecoder.GifHeaderParserPool parserPool;
  @Mock private ByteBufferGifDecoder.GifDecoderFactory decoderFactory;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);

    gifHeader = Mockito.spy(new GifHeader());
    when(parser.parseHeader()).thenReturn(gifHeader);
    when(parserPool.obtain(isA(ByteBuffer.class))).thenReturn(parser);

<|edits_diff|>
--- library/test/src/test/java/com/bumptech/glide/load/resource/gif/ByteBufferGifDecoderTest.java
+++ library/test/src/test/java/com/bumptech/glide/load/resource/gif/ByteBufferGifDecoderTest.java
@@ -8,6 +8,7 @@
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;

 import com.bumptech.glide.gifdecoder.GifDecoder;
 import com.bumptech.glide.gifdecoder.GifHeader;
@@ -31,6 +32,7 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;

 @RunWith(RobolectricTestRunner.class)
 @Config(sdk = 18, shadows = GlideShadowLooper.class)
<|current_version|>
package com.bumptech.glide.load.resource.gif;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;

import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.gifdecoder.GifHeaderParser;
import com.bumptech.glide.load.ImageHeaderParser;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;
import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;
import com.bumptech.glide.tests.GlideShadowLooper;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 18, shadows = GlideShadowLooper.class)
public class ByteBufferGifDecoderTest {
  private static final byte[] GIF_HEADER = new byte[] {0x47, 0x49, 0x46};
  private static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;

  private ByteBufferGifDecoder decoder;
  private GifHeader gifHeader;
  private Options options;

  @Mock private BitmapPool bitmapPool;
  @Mock private GifHeaderParser parser;
  @Mock private GifDecoder gifDecoder;
  @Mock private ByteBufferGifDecoder.GifHeaderParserPool parserPool;
  @Mock private ByteBufferGifDecoder.GifDecoderFactory decoderFactory;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);

    gifHeader = Mockito.spy(new GifHeader());
    when(parser.parseHeader()).thenReturn(gifHeader);
    when(parserPool.obtain(isA(ByteBuffer.class))).thenReturn(parser);

<|next_version|>
package com.bumptech.glide.load.resource.gif;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;

import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.gifdecoder.GifHeaderParser;
import com.bumptech.glide.load.ImageHeaderParser;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;
import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;
import com.bumptech.glide.tests.GlideShadowLooper;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;

@LooperMode(LEGACY)
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 18, shadows = GlideShadowLooper.class)
public class ByteBufferGifDecoderTest {
  private static final byte[] GIF_HEADER = new byte[] {0x47, 0x49, 0x46};
  private static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;

  private ByteBufferGifDecoder decoder;
  private GifHeader gifHeader;
  private Options options;

  @Mock private BitmapPool bitmapPool;
  @Mock private GifHeaderParser parser;
  @Mock private GifDecoder gifDecoder;
  @Mock private ByteBufferGifDecoder.GifHeaderParserPool parserPool;
  @Mock private ByteBufferGifDecoder.GifDecoderFactory decoderFactory;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);

    gifHeader = Mockito.spy(new GifHeader());
    when(parser.parseHeader()).thenReturn(gifHeader);
    when(parserPool.obtain(isA(ByteBuffer.class))).thenReturn(parser);


Add global/activity/fragment scoped RequestListener API to Glide.

<|original_code|>
package com.bumptech.glide;

import android.content.Context;
import android.content.ContextWrapper;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.ImageViewTargetFactory;
import com.bumptech.glide.request.target.ViewTarget;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Global context for all loads in Glide containing and exposing the various registries and classes
 * required to load resources.
 */
public class GlideContext extends ContextWrapper {
  @VisibleForTesting
  static final TransitionOptions<?, ?> DEFAULT_TRANSITION_OPTIONS =
      new GenericTransitionOptions<>();
  private final Handler mainHandler;
  private final ArrayPool arrayPool;
  private final Registry registry;
  private final ImageViewTargetFactory imageViewTargetFactory;
  private final RequestOptions defaultRequestOptions;
  private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions;
  private final Engine engine;
  private final int logLevel;

  public GlideContext(
      @NonNull Context context,
      @NonNull ArrayPool arrayPool,
      @NonNull Registry registry,
      @NonNull ImageViewTargetFactory imageViewTargetFactory,
      @NonNull RequestOptions defaultRequestOptions,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NonNull Engine engine,
      int logLevel) {
    super(context.getApplicationContext());
    this.arrayPool = arrayPool;
    this.registry = registry;
    this.imageViewTargetFactory = imageViewTargetFactory;
    this.defaultRequestOptions = defaultRequestOptions;
    this.defaultTransitionOptions = defaultTransitionOptions;
    this.engine = engine;
    this.logLevel = logLevel;

    mainHandler = new Handler(Looper.getMainLooper());
  }

  public RequestOptions getDefaultRequestOptions() {
    return defaultRequestOptions;
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public <T> TransitionOptions<?, T> getDefaultTransitionOptions(@NonNull Class<T> transcodeClass) {
    TransitionOptions<?, ?> result = defaultTransitionOptions.get(transcodeClass);
    if (result == null) {
      for (Entry<Class<?>, TransitionOptions<?, ?>> value : defaultTransitionOptions.entrySet()) {
        if (value.getKey().isAssignableFrom(transcodeClass)) {
          result = value.getValue();
        }
      }
    }
    if (result == null) {
      result = DEFAULT_TRANSITION_OPTIONS;
    }
    return (TransitionOptions<?, T>) result;
  }

  @NonNull
<|edits_diff|>
--- library/src/main/java/com/bumptech/glide/GlideContext.java
+++ library/src/main/java/com/bumptech/glide/GlideContext.java
@@ -9,9 +9,11 @@
 import android.widget.ImageView;
 import com.bumptech.glide.load.engine.Engine;
 import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
+import com.bumptech.glide.request.RequestListener;
 import com.bumptech.glide.request.RequestOptions;
 import com.bumptech.glide.request.target.ImageViewTargetFactory;
 import com.bumptech.glide.request.target.ViewTarget;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;

@@ -28,6 +30,7 @@
   private final Registry registry;
   private final ImageViewTargetFactory imageViewTargetFactory;
   private final RequestOptions defaultRequestOptions;
+  private final List<RequestListener<Object>> defaultRequestListeners;
   private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions;
   private final Engine engine;
   private final int logLevel;
@@ -39,6 +42,7 @@
       @NonNull ImageViewTargetFactory imageViewTargetFactory,
       @NonNull RequestOptions defaultRequestOptions,
       @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
+      @NonNull List<RequestListener<Object>> defaultRequestListeners,
       @NonNull Engine engine,
       int logLevel) {
     super(context.getApplicationContext());
@@ -46,6 +50,7 @@
     this.registry = registry;
     this.imageViewTargetFactory = imageViewTargetFactory;
     this.defaultRequestOptions = defaultRequestOptions;
+    this.defaultRequestListeners = defaultRequestListeners;
     this.defaultTransitionOptions = defaultTransitionOptions;
     this.engine = engine;
     this.logLevel = logLevel;
<|current_version|>
package com.bumptech.glide;

import android.content.Context;
import android.content.ContextWrapper;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.ImageViewTargetFactory;
import com.bumptech.glide.request.target.ViewTarget;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Global context for all loads in Glide containing and exposing the various registries and classes
 * required to load resources.
 */
public class GlideContext extends ContextWrapper {
  @VisibleForTesting
  static final TransitionOptions<?, ?> DEFAULT_TRANSITION_OPTIONS =
      new GenericTransitionOptions<>();
  private final Handler mainHandler;
  private final ArrayPool arrayPool;
  private final Registry registry;
  private final ImageViewTargetFactory imageViewTargetFactory;
  private final RequestOptions defaultRequestOptions;
  private final List<RequestListener<Object>> defaultRequestListeners;
  private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions;
  private final Engine engine;
  private final int logLevel;

  public GlideContext(
      @NonNull Context context,
      @NonNull ArrayPool arrayPool,
      @NonNull Registry registry,
      @NonNull ImageViewTargetFactory imageViewTargetFactory,
      @NonNull RequestOptions defaultRequestOptions,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NonNull List<RequestListener<Object>> defaultRequestListeners,
      @NonNull Engine engine,
      int logLevel) {
    super(context.getApplicationContext());
    this.arrayPool = arrayPool;
    this.registry = registry;
    this.imageViewTargetFactory = imageViewTargetFactory;
    this.defaultRequestOptions = defaultRequestOptions;
    this.defaultRequestListeners = defaultRequestListeners;
    this.defaultTransitionOptions = defaultTransitionOptions;
    this.engine = engine;
    this.logLevel = logLevel;

    mainHandler = new Handler(Looper.getMainLooper());
  }

  public RequestOptions getDefaultRequestOptions() {
    return defaultRequestOptions;
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public <T> TransitionOptions<?, T> getDefaultTransitionOptions(@NonNull Class<T> transcodeClass) {
    TransitionOptions<?, ?> result = defaultTransitionOptions.get(transcodeClass);
    if (result == null) {
      for (Entry<Class<?>, TransitionOptions<?, ?>> value : defaultTransitionOptions.entrySet()) {
        if (value.getKey().isAssignableFrom(transcodeClass)) {
          result = value.getValue();
        }
      }
    }
    if (result == null) {
      result = DEFAULT_TRANSITION_OPTIONS;
    }
    return (TransitionOptions<?, T>) result;
  }

  @NonNull
<|next_version|>
package com.bumptech.glide;

import android.content.Context;
import android.content.ContextWrapper;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.ImageViewTargetFactory;
import com.bumptech.glide.request.target.ViewTarget;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Global context for all loads in Glide containing and exposing the various registries and classes
 * required to load resources.
 */
public class GlideContext extends ContextWrapper {
  @VisibleForTesting
  static final TransitionOptions<?, ?> DEFAULT_TRANSITION_OPTIONS =
      new GenericTransitionOptions<>();
  private final Handler mainHandler;
  private final ArrayPool arrayPool;
  private final Registry registry;
  private final ImageViewTargetFactory imageViewTargetFactory;
  private final RequestOptions defaultRequestOptions;
  private final List<RequestListener<Object>> defaultRequestListeners;
  private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions;
  private final Engine engine;
  private final int logLevel;

  public GlideContext(
      @NonNull Context context,
      @NonNull ArrayPool arrayPool,
      @NonNull Registry registry,
      @NonNull ImageViewTargetFactory imageViewTargetFactory,
      @NonNull RequestOptions defaultRequestOptions,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NonNull List<RequestListener<Object>> defaultRequestListeners,
      @NonNull Engine engine,
      int logLevel) {
    super(context.getApplicationContext());
    this.arrayPool = arrayPool;
    this.registry = registry;
    this.imageViewTargetFactory = imageViewTargetFactory;
    this.defaultRequestOptions = defaultRequestOptions;
    this.defaultRequestListeners = defaultRequestListeners;
    this.defaultTransitionOptions = defaultTransitionOptions;
    this.engine = engine;
    this.logLevel = logLevel;

    mainHandler = new Handler(Looper.getMainLooper());
  }

  public List<RequestListener<Object>> getDefaultRequestListeners() {
    return defaultRequestListeners;
  }

  public RequestOptions getDefaultRequestOptions() {
    return defaultRequestOptions;
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public <T> TransitionOptions<?, T> getDefaultTransitionOptions(@NonNull Class<T> transcodeClass) {
    TransitionOptions<?, ?> result = defaultTransitionOptions.get(transcodeClass);
    if (result == null) {
      for (Entry<Class<?>, TransitionOptions<?, ?>> value : defaultTransitionOptions.entrySet()) {
        if (value.getKey().isAssignableFrom(transcodeClass)) {
          result = value.getValue();
        }
      }
    }
    if (result == null) {
      result = DEFAULT_TRANSITION_OPTIONS;
    }
    return (TransitionOptions<?, T>) result;
  }

  @NonNull


TearDownGlide in the GifDrawable unit test.

<|original_code|>
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.view.View;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.resource.gif.GifDrawableTest.BitmapTrackingShadowCanvas;
import com.bumptech.glide.tests.GlideShadowLooper;
import com.bumptech.glide.tests.Util;
import com.bumptech.glide.util.Preconditions;
import java.util.HashSet;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowCanvas;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18,
    shadows = { GlideShadowLooper.class, BitmapTrackingShadowCanvas.class })
public class GifDrawableTest {
  private GifDrawable drawable;
  private int frameHeight;
  private int frameWidth;
  private Bitmap firstFrame;
  private int initialSdkVersion;

  @Mock private Drawable.Callback cb;
  @Mock private GifFrameLoader frameLoader;
  @Mock private Paint paint;
  @Mock private Transformation<Bitmap> transformation;
  private Application context;

  private static Paint isAPaint() {
    return isA(Paint.class);
  }

  private static Rect isARect() {
    return isA(Rect.class);
  }

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    context = RuntimeEnvironment.application;
<|edits_diff|>
--- library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifDrawableTest.java
+++ library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifDrawableTest.java
@@ -20,12 +20,14 @@
 import com.bumptech.glide.load.Transformation;
 import com.bumptech.glide.load.resource.gif.GifDrawableTest.BitmapTrackingShadowCanvas;
 import com.bumptech.glide.tests.GlideShadowLooper;
+import com.bumptech.glide.tests.TearDownGlide;
 import com.bumptech.glide.tests.Util;
 import com.bumptech.glide.util.Preconditions;
 import java.util.HashSet;
 import java.util.Set;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
<|current_version|>
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.view.View;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.resource.gif.GifDrawableTest.BitmapTrackingShadowCanvas;
import com.bumptech.glide.tests.GlideShadowLooper;
import com.bumptech.glide.tests.TearDownGlide;
import com.bumptech.glide.tests.Util;
import com.bumptech.glide.util.Preconditions;
import java.util.HashSet;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowCanvas;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18,
    shadows = { GlideShadowLooper.class, BitmapTrackingShadowCanvas.class })
public class GifDrawableTest {
  private GifDrawable drawable;
  private int frameHeight;
  private int frameWidth;
  private Bitmap firstFrame;
  private int initialSdkVersion;

  @Mock private Drawable.Callback cb;
  @Mock private GifFrameLoader frameLoader;
  @Mock private Paint paint;
  @Mock private Transformation<Bitmap> transformation;
  private Application context;

  private static Paint isAPaint() {
    return isA(Paint.class);
  }

  private static Rect isARect() {
    return isA(Rect.class);
  }

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    context = RuntimeEnvironment.application;
<|next_version|>
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.view.View;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.resource.gif.GifDrawableTest.BitmapTrackingShadowCanvas;
import com.bumptech.glide.tests.GlideShadowLooper;
import com.bumptech.glide.tests.TearDownGlide;
import com.bumptech.glide.tests.Util;
import com.bumptech.glide.util.Preconditions;
import java.util.HashSet;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowCanvas;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18,
    shadows = { GlideShadowLooper.class, BitmapTrackingShadowCanvas.class })
public class GifDrawableTest {
  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();

  private GifDrawable drawable;
  private int frameHeight;
  private int frameWidth;
  private Bitmap firstFrame;
  private int initialSdkVersion;

  @Mock private Drawable.Callback cb;
  @Mock private GifFrameLoader frameLoader;
  @Mock private Paint paint;
  @Mock private Transformation<Bitmap> transformation;
  private Application context;

  private static Paint isAPaint() {
    return isA(Paint.class);
  }

  private static Rect isARect() {
    return isA(Rect.class);
  }

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    context = RuntimeEnvironment.application;


Check isLaidOut/isLayoutRequested in ViewTarget.

<|original_code|>

  @Test
  public void testReturnsNullFromGetPreloadSizeBeforeHasSize() {
    assertNull(provider.getPreloadSize(new Object(), 0, 0));
  }

  @Test
  public void testReturnsValidSizeFromGetPreloadSizeAfterHasSize() {
    int width = 4123;
    int height = 342;
    provider.onSizeReady(width, height);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testDoesNotObtainSizeFromViewOnceSizeIsSet() {
    int width = 123;
    int height = 456;
    provider.onSizeReady(width, height);
    view.setLayoutParams(new ViewGroup.LayoutParams(1, 1));

    provider.setView(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testCanObtainFixedSizeFromView() {
    int width = 123;
    int height = 456;
    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));

    provider.setView(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testIgnoresNewViewIfAlreadyWaitingOnSizeOfAnotherView() {
    provider.setView(view);

    View newView = new View(RuntimeEnvironment.application);
    newView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
    provider.setView(newView);

    assertNull(provider.getPreloadSize(new Object(), 0, 0));
  }

  @Test
  public void testCanObtainSizeFromViewWhenGivenViewInConstructor() {
    int width = 100;
    int height = 200;
    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));

    provider = new ViewPreloadSizeProvider<>(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }
}

<|edits_diff|>
--- library/src/test/java/com/bumptech/glide/util/ViewPreloadSizeProviderTest.java
+++ library/src/test/java/com/bumptech/glide/util/ViewPreloadSizeProviderTest.java
@@ -20,6 +20,7 @@
     int height = 456;
     provider.onSizeReady(width, height);
     view.setLayoutParams(new ViewGroup.LayoutParams(1, 1));
+    view.layout(0, 0, 1, 1);

     provider.setView(view);

@@ -32,6 +33,7 @@
     int width = 123;
     int height = 456;
     view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
+    view.layout(0, 0, width, height);

     provider.setView(view);

<|current_version|>

  @Test
  public void testReturnsNullFromGetPreloadSizeBeforeHasSize() {
    assertNull(provider.getPreloadSize(new Object(), 0, 0));
  }

  @Test
  public void testReturnsValidSizeFromGetPreloadSizeAfterHasSize() {
    int width = 4123;
    int height = 342;
    provider.onSizeReady(width, height);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testDoesNotObtainSizeFromViewOnceSizeIsSet() {
    int width = 123;
    int height = 456;
    provider.onSizeReady(width, height);
    view.setLayoutParams(new ViewGroup.LayoutParams(1, 1));
    view.layout(0, 0, 1, 1);

    provider.setView(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testCanObtainFixedSizeFromView() {
    int width = 123;
    int height = 456;
    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
    view.layout(0, 0, width, height);

    provider.setView(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testIgnoresNewViewIfAlreadyWaitingOnSizeOfAnotherView() {
    provider.setView(view);

    View newView = new View(RuntimeEnvironment.application);
    newView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
    provider.setView(newView);

    assertNull(provider.getPreloadSize(new Object(), 0, 0));
  }

  @Test
  public void testCanObtainSizeFromViewWhenGivenViewInConstructor() {
    int width = 100;
    int height = 200;
    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));

    provider = new ViewPreloadSizeProvider<>(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }
}

<|next_version|>

  @Test
  public void testReturnsNullFromGetPreloadSizeBeforeHasSize() {
    assertNull(provider.getPreloadSize(new Object(), 0, 0));
  }

  @Test
  public void testReturnsValidSizeFromGetPreloadSizeAfterHasSize() {
    int width = 4123;
    int height = 342;
    provider.onSizeReady(width, height);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testDoesNotObtainSizeFromViewOnceSizeIsSet() {
    int width = 123;
    int height = 456;
    provider.onSizeReady(width, height);
    view.setLayoutParams(new ViewGroup.LayoutParams(1, 1));
    view.layout(0, 0, 1, 1);

    provider.setView(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testCanObtainFixedSizeFromView() {
    int width = 123;
    int height = 456;
    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
    view.layout(0, 0, width, height);

    provider.setView(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }

  @Test
  public void testIgnoresNewViewIfAlreadyWaitingOnSizeOfAnotherView() {
    provider.setView(view);

    View newView = new View(RuntimeEnvironment.application);
    newView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
    provider.setView(newView);

    assertNull(provider.getPreloadSize(new Object(), 0, 0));
  }

  @Test
  public void testCanObtainSizeFromViewWhenGivenViewInConstructor() {
    int width = 100;
    int height = 200;
    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
    view.layout(0, 0, width, height);

    provider = new ViewPreloadSizeProvider<>(view);

    int[] size = provider.getPreloadSize(new Object(), 0, 0);
    assertThat(size).asList().containsExactly(width, height);
  }
}