chromium/chromecast/base/java/src/org/chromium/chromecast/base/OwnedScope.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chromecast.base;

/**
 * A Scope that encapsulates up to one other Scope at a time.
 *
 * If the set() method is used to assign an owned Scope, any previous Scope assigned to this
 * OwnedScope is closed.
 *
 * This can be useful for turning APIs with start/stop, or connected/disconnected method pairs into
 * safely implementing Observables.
 *
 * For example, you can easily implement an API that encapsulates a ServiceConnection in an
 * Observable, which notifies subscribers when the connection is established and closes the scope
 * safely when the connection is ended.
 *
 *   // A static method on a Service implementation like this can provide an easy connection API.
 *   public static Observable<IFoo> connect(Context context) {
 *       return observer -> {
 *           // Use an OwnedScope to avoid a null check in onServiceDisconnected().
 *           OwnedScope subscription = new OwnedScope();
 *           ServiceConnection connection = new ServiceConnection() {
 *               @Override
 *               public void onServiceConnected(ComponentName componentName, IBinder binder) {
 *                   // If, for some reason, onServiceConnected() is called twice, the new
 *                   // connection overrides the old one and the observer is notified correctly.
 *                   subscription.set(observer.open((IFoo) binder));
 *               }
 *               @Override
 *               public void onServiceDisconnected(ComponentName componentName) {
 *                   // This is safe no matter how many times onServiceConnected() was invoked.
 *                   subscription.close();
 *               }
 *           };
 *           context.bindService(new Intent(context, FooService.class), connection);
 *           return subscription.and(() -> context.unbindService(connection));
 *       };
 *   }
 *
 * An OwnedScope can also be useful for cleanup in clients for such APIs:
 *
 *   class FooClient extends Scope {
 *       private final OwnedScope mConnection = new OwnedScope;
 *       Foo mFoo;
 *
 *       public void establishConnection() {
 *           mConnection.set(FooService.connect(context).subscribe((IFoo foo) {
 *               mFoo = foo;
 *               return () -> mFoo = null;
 *           }));
 *       }
 *
 *       @Override
 *       public void close() {
 *           mConnection.close();
 *       }
 *   }
 */
public class OwnedScope implements Scope {
    private Scope mScope;

    public OwnedScope() {}

    public OwnedScope(Scope scope) {
        mScope = scope;
    }

    public void set(Scope scope) {
        if (mScope != null && scope != mScope) mScope.close();
        mScope = scope;
    }

    @Override
    public void close() {
        if (mScope == null) return;
        mScope.close();
        mScope = null;
    }
}