Merge "Revert "Add API to communicate if the car supports AppDrivenRefresh"" into androidx-main
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
index 49f2220..79a9f91f 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
@@ -20,8 +20,8 @@
 import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
 import android.util.Size
 import android.view.Surface
+import androidx.camera.core.CameraEffect
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
-import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.ImageFormatConstants
@@ -304,7 +304,7 @@
     private fun createSurfaceOutput(surface: Surface = mock(Surface::class.java)) =
         SurfaceOutputImpl(
             surface,
-            SurfaceProcessor.PREVIEW,
+            CameraEffect.PREVIEW,
             ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
             Size(WIDTH, HEIGHT),
             USE_SURFACE_TEXTURE_TRANSFORM,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
index 2445aa0..4d7c8ea 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
@@ -13,34 +13,177 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.camera.core;
 
+import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
+import static androidx.core.util.Preconditions.checkState;
+
+import android.os.Build;
+
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
 
 /**
- * The effect interface which all other effects are built on top of.
+ * A CameraX post-processing effects.
  *
- * <p>A CameraEffect provides functionality to inject post processing to camera output.
+ * <p>A {@link CameraEffect} class contains two types of information, the processor and the
+ * configuration.
+ * <ul>
+ * <li> The processor is an implementation of a CameraX interface e.g. {@link SurfaceProcessor}.
+ * It consumes original camera frames from CameraX, applies the effect, and returns the processed
+ * frames back to CameraX.
+ * <li> The configuration provides information on how the processor should be injected into the
+ * CameraX pipeline. For example, the target {@link UseCase}s where the effect should be applied.
+ * </ul>
  *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface CameraEffect {
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class CameraEffect {
 
     /**
-     * Bitmask options for the targets of the effect.
+     * Bitmask options for the effect targets.
      *
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @IntDef(flag = true, value = {SurfaceProcessor.PREVIEW, SurfaceProcessor.VIDEO_CAPTURE,
-            ImageEffect.IMAGE_CAPTURE})
-    @interface Targets {
+    @IntDef(flag = true, value = {PREVIEW, VIDEO_CAPTURE, IMAGE_CAPTURE})
+    public @interface Targets {
+    }
+
+    /**
+     * Bitmask option to indicate that CameraX should apply this effect to {@link Preview}.
+     */
+    public static final int PREVIEW = 1;
+
+    /**
+     * Bitmask option to indicate that CameraX should apply this effect to {@code VideoCapture}.
+     */
+    public static final int VIDEO_CAPTURE = 1 << 1;
+
+    /**
+     * Bitmask option to indicate that CameraX should apply this effect to {@link ImageCapture}.
+     */
+    public static final int IMAGE_CAPTURE = 1 << 2;
+
+    @Targets
+    private final int mTargets;
+    @NonNull
+    private final Executor mProcessorExecutor;
+    @Nullable
+    private final SurfaceProcessor mSurfaceProcessor;
+
+    /**
+     * Private constructor as a workaround to allow @Nullable annotation on final fields.
+     */
+    @SuppressWarnings("UnusedMethod") // TODO: remove once we add {@link ImageProcessor}.
+    private CameraEffect(@Targets int targets) {
+        mTargets = targets;
+        mProcessorExecutor = mainThreadExecutor();
+        mSurfaceProcessor = null;
+    }
+
+    /**
+     * @param targets           the target {@link UseCase} to which this effect should be applied.
+     * @param processorExecutor the {@link Executor} on which the processor will be invoked.
+     * @param surfaceProcessor  a {@link SurfaceProcessor} implementation.
+     */
+    protected CameraEffect(
+            @Targets int targets,
+            @NonNull Executor processorExecutor,
+            @NonNull SurfaceProcessor surfaceProcessor) {
+        mTargets = targets;
+        mProcessorExecutor = processorExecutor;
+        mSurfaceProcessor = surfaceProcessor;
+    }
+
+    /**
+     * Ges the target {@link UseCase}s of this effect.
+     */
+    @Targets
+    public int getTargets() {
+        return mTargets;
+    }
+
+    /**
+     * Gets the {@link Executor} for calling processors.
+     *
+     * <p>This method returns the value set via {@link Builder#setSurfaceProcessor}.
+     */
+    @NonNull
+    public Executor getProcessorExecutor() {
+        return mProcessorExecutor;
+    }
+
+    /**
+     * Gets the {@link SurfaceProcessor} associated with this effect.
+     *
+     * <p>This method returns the value set via {@link Builder#setSurfaceProcessor}.
+     */
+    @Nullable
+    public SurfaceProcessor getSurfaceProcessor() {
+        return mSurfaceProcessor;
+    }
+
+    /**
+     * Builder class for {@link CameraEffect}.
+     */
+    public static class Builder {
+        @Targets
+        private final int mTargets;
+        @Nullable
+        private Executor mProcessorExecutor;
+        @Nullable
+        private SurfaceProcessor mSurfaceProcessor;
+
+        /**
+         * @param targets the target {@link UseCase} of the Effect. e.g. if the
+         *                value is {@link #PREVIEW}, CameraX will apply the effect to
+         *                {@link Preview}.
+         */
+        public Builder(@Targets int targets) {
+            mTargets = targets;
+        }
+
+        /**
+         * Sets a {@link SurfaceProcessor} for the effect.
+         *
+         * <p>Once the effect is active, CameraX will send original camera frames to the
+         * {@link SurfaceProcessor} on the {@link Executor}, and deliver the processed output
+         * frames to the app.
+         *
+         * @param executor  on which the {@link SurfaceProcessor} will be invoked.
+         * @param processor the post processor to be injected into CameraX pipeline.
+         */
+        @NonNull
+        public Builder setSurfaceProcessor(@NonNull Executor executor,
+                @NonNull SurfaceProcessor processor) {
+            mProcessorExecutor = executor;
+            mSurfaceProcessor = processor;
+            return this;
+        }
+
+        /**
+         * Builds a {@link CameraEffect} instance.
+         *
+         * <p>CameraX supports a selected set of configuration/processor combinations. This method
+         * throws a {@link UnsupportedOperationException} if the current combination is not
+         * supported.
+         */
+        @NonNull
+        public CameraEffect build() {
+            checkState(mProcessorExecutor != null && mSurfaceProcessor != null,
+                    "Must set a processor.");
+            return new CameraEffect(mTargets, mProcessorExecutor, mSurfaceProcessor);
+        }
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java b/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
deleted file mode 100644
index ba6b61f..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * 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 androidx.camera.core;
-
-import static androidx.camera.core.SurfaceProcessor.PREVIEW;
-import static androidx.core.util.Preconditions.checkArgument;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * A bundle of {@link CameraEffect}s and their targets.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class EffectBundle {
-
-    private final Map<Integer, CameraEffect> mEffects;
-
-    private final Executor mExecutor;
-
-    EffectBundle(@NonNull Map<Integer, CameraEffect> effects, @NonNull Executor executor) {
-        mEffects = effects;
-        mExecutor = executor;
-    }
-
-    /**
-     * Gets the {@link CameraEffect} and their targets.
-     */
-    @NonNull
-    public Map<Integer, CameraEffect> getEffects() {
-        return new HashMap<>(mEffects);
-    }
-
-    /**
-     * Gets the {@link Executor} used for calling the {@link CameraEffect}.
-     */
-    @NonNull
-    public Executor getExecutor() {
-        return mExecutor;
-    }
-
-    /**
-     * Builder class for {@link EffectBundle}.
-     */
-    public static class Builder {
-
-        private final Map<Integer, CameraEffect> mEffects;
-        private final Executor mExecutor;
-
-        /**
-         * Creates a {@link EffectBundle} builder.
-         *
-         * @param executor on which the {@link CameraEffect}s will be invoked.
-         */
-        public Builder(@NonNull Executor executor) {
-            mEffects = new HashMap<>();
-            mExecutor = executor;
-        }
-
-        /**
-         * Adds a {@link CameraEffect} with its targets.
-         *
-         * @param targets      on which the effect will be applied. CameraX only supports
-         *                     {@link SurfaceProcessor#PREVIEW} for now.
-         * @param cameraEffect the effect implementation.
-         * @throws IllegalArgumentException if the configuration is illegal.
-         */
-        @NonNull
-        public Builder addEffect(
-                @CameraEffect.Targets int targets,
-                @NonNull CameraEffect cameraEffect) {
-            checkArgument(!mEffects.containsKey(targets), "The target already has an effect");
-            checkArgument(targets == PREVIEW, "Only allows PREVIEW target.");
-            if (cameraEffect instanceof SurfaceProcessor) {
-                mEffects.put(targets, cameraEffect);
-            } else {
-                throw new UnsupportedOperationException(
-                        "CameraX only supports SurfaceProcessor for now.");
-            }
-            return this;
-        }
-
-        /**
-         * Builds the {@link EffectBundle}.
-         *
-         * @throws IllegalArgumentException if the bundle contains no effect.
-         */
-        @NonNull
-        public EffectBundle build() {
-            checkArgument(mEffects.size() > 0, "The bundle cannot be empty");
-            return new EffectBundle(new HashMap<>(mEffects), mExecutor);
-        }
-    }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessor.java
similarity index 82%
rename from camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java
rename to camera/camera-core/src/main/java/androidx/camera/core/ImageProcessor.java
index 9e32538..03d7ef7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessor.java
@@ -24,12 +24,6 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface ImageEffect extends CameraEffect {
-
-    /**
-     * Bitmask option to indicate that CameraX applies this effect to {@link ImageCapture}.
-     */
-    int IMAGE_CAPTURE = 1 << 2;
-
+public interface ImageProcessor {
     // TODO(b/229629890): create the public interface for post-processing images.
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index 062cdea..dab5cfd 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -312,7 +312,7 @@
         // Create nodes and edges.
         mNode = new SurfaceProcessorNode(camera, USE_SURFACE_TEXTURE_TRANSFORM, mSurfaceProcessor);
         SettableSurface cameraSurface = new SettableSurface(
-                SurfaceProcessor.PREVIEW,
+                CameraEffect.PREVIEW,
                 resolution,
                 ImageFormat.PRIVATE,
                 new Matrix(),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
index fd55b33..56e6aec 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
@@ -34,17 +34,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface SurfaceProcessor extends CameraEffect {
-
-    /**
-     * Bitmask option to indicate that CameraX applies this effect to {@link Preview}.
-     */
-    int PREVIEW = 1;
-
-    /**
-     * Bitmask option to indicate that CameraX applies this effect to {@code VideoCapture}.
-     */
-    int VIDEO_CAPTURE = 1 << 1;
+public interface SurfaceProcessor {
 
     /**
      * Invoked when CameraX requires an input {@link Surface} for reading original frames.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
index d5a76f2..3848aa5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
@@ -37,21 +37,18 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public final class UseCaseGroup {
-
     @Nullable
     private final ViewPort mViewPort;
-
     @NonNull
     private final List<UseCase> mUseCases;
-
     @NonNull
-    private final EffectBundle mEffectBundle;
+    private final List<CameraEffect> mEffects;
 
     UseCaseGroup(@Nullable ViewPort viewPort, @NonNull List<UseCase> useCases,
-            @NonNull EffectBundle effectBundle) {
+            @NonNull List<CameraEffect> effects) {
         mViewPort = viewPort;
         mUseCases = useCases;
-        mEffectBundle = effectBundle;
+        mEffects = effects;
     }
 
     /**
@@ -71,30 +68,27 @@
     }
 
     /**
-     * Gets the {@link EffectBundle}.
+     * Gets the {@link CameraEffect}s.
      *
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @NonNull
-    public EffectBundle getEffectBundle() {
-        return mEffectBundle;
+    public List<CameraEffect> getEffects() {
+        return mEffects;
     }
 
     /**
      * A builder for generating {@link UseCaseGroup}.
      */
     public static final class Builder {
-
         private ViewPort mViewPort;
-
         private final List<UseCase> mUseCases;
-
-        @Nullable
-        private EffectBundle mEffectBundle;
+        private final List<CameraEffect> mEffects;
 
         public Builder() {
             mUseCases = new ArrayList<>();
+            mEffects = new ArrayList<>();
         }
 
         /**
@@ -107,17 +101,17 @@
         }
 
         /**
-         * Sets the {@link EffectBundle} for the {@link UseCase}s.
+         * Adds a {@link CameraEffect} to the collection
          *
-         * <p>Once set, CameraX will use the {@link SurfaceProcessor}s to process the outputs of
+         * <p>Once added, CameraX will use the {@link CameraEffect}s to process the outputs of
          * the {@link UseCase}s.
          *
          * @hide
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @NonNull
-        public Builder setEffectBundle(@NonNull EffectBundle effectBundle) {
-            mEffectBundle = effectBundle;
+        public Builder addEffect(@NonNull CameraEffect cameraEffect) {
+            mEffects.add(cameraEffect);
             return this;
         }
 
@@ -136,8 +130,7 @@
         @NonNull
         public UseCaseGroup build() {
             checkArgument(!mUseCases.isEmpty(), "UseCase must not be empty.");
-            return new UseCaseGroup(mViewPort, mUseCases, mEffectBundle);
+            return new UseCaseGroup(mViewPort, mUseCases, mEffects);
         }
     }
-
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index a19cccb..a2da5d9 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.core.internal;
 
+import static java.util.Collections.emptyList;
+import static java.util.Objects.requireNonNull;
+
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -33,11 +36,9 @@
 import androidx.camera.core.CameraEffect;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
-import androidx.camera.core.EffectBundle;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Logger;
 import androidx.camera.core.Preview;
-import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.impl.AttachedSurfaceInfo;
@@ -52,19 +53,16 @@
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.processing.SurfaceProcessorInternal;
 import androidx.camera.core.processing.SurfaceProcessorWithExecutor;
 import androidx.core.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.Executor;
 
 /**
  * A {@link CameraInternal} adapter which checks that the UseCases to make sure that the resolutions
@@ -97,8 +95,8 @@
     private ViewPort mViewPort;
 
     @GuardedBy("mLock")
-    @Nullable
-    private EffectBundle mEffectBundle;
+    @NonNull
+    private List<CameraEffect> mEffects = emptyList();
 
     // Additional configs to apply onto the UseCases when added to this Camera
     @GuardedBy("mLock")
@@ -176,9 +174,9 @@
     /**
      * Set the effects that will be used for the {@link UseCase} attached to the camera.
      */
-    public void setEffectBundle(@Nullable EffectBundle effectBundle) {
+    public void setEffects(@Nullable List<CameraEffect> effects) {
         synchronized (mLock) {
-            mEffectBundle = effectBundle;
+            mEffects = effects;
         }
     }
 
@@ -202,8 +200,8 @@
             }
 
             List<UseCase> allUseCases = new ArrayList<>(mUseCases);
-            List<UseCase> requiredExtraUseCases = Collections.emptyList();
-            List<UseCase> removedExtraUseCases = Collections.emptyList();
+            List<UseCase> requiredExtraUseCases = emptyList();
+            List<UseCase> removedExtraUseCases = emptyList();
 
             if (isCoexistingPreviewImageCaptureRequired()) {
                 // Collects all use cases that will be finally bound by the application
@@ -244,7 +242,7 @@
                 throw new CameraException(e.getMessage());
             }
             updateViewPort(suggestedResolutionsMap, useCases);
-            updateEffects(mEffectBundle, useCases);
+            updateEffects(mEffects, useCases);
 
             // Saves the updated extra use cases set after confirming the use case combination
             // can be supported.
@@ -293,7 +291,7 @@
 
                 try {
                     // Calls addUseCases with empty list to add required extra fake use case.
-                    addUseCases(Collections.emptyList());
+                    addUseCases(emptyList());
                 } catch (CameraException e) {
                     // This should not happen because the extra fake use case should be only
                     // added to replace the removed one which the use case combination can be
@@ -442,27 +440,25 @@
     }
 
     @VisibleForTesting
-    static void updateEffects(@Nullable EffectBundle effectBundle,
+    static void updateEffects(@NonNull List<CameraEffect> effects,
             @NonNull Collection<UseCase> useCases) {
-        Map<Integer, CameraEffect> effectsWithExecutors = new HashMap<>();
-        // Wrap external effects with the executor to make sure they are thread safe.
-        if (effectBundle != null) {
-            Executor executor = effectBundle.getExecutor();
-            for (Map.Entry<Integer, CameraEffect> entry : effectBundle.getEffects().entrySet()) {
-                CameraEffect effect = entry.getValue();
-                int targets = entry.getKey();
-                if (effect instanceof SurfaceProcessor) {
-                    effectsWithExecutors.put(targets, new SurfaceProcessorWithExecutor(
-                            (SurfaceProcessor) effect, executor));
-                }
-            }
+        Map<Integer, CameraEffect> effectsByTargets = new HashMap<>();
+        for (CameraEffect effect : effects) {
+            effectsByTargets.put(effect.getTargets(), effect);
         }
+
         // Set effects on the UseCases. This also removes existing effects if necessary.
         for (UseCase useCase : useCases) {
             if (useCase instanceof Preview) {
-                ((Preview) useCase).setProcessor(
-                        (SurfaceProcessorInternal) effectsWithExecutors.get(
-                                SurfaceProcessor.PREVIEW));
+                Preview preview = ((Preview) useCase);
+                CameraEffect effect = effectsByTargets.get(CameraEffect.PREVIEW);
+                if (effect == null) {
+                    preview.setProcessor(null);
+                    continue;
+                }
+                preview.setProcessor(new SurfaceProcessorWithExecutor(
+                        requireNonNull(effect.getSurfaceProcessor()),
+                        effect.getProcessorExecutor()));
             }
         }
     }
@@ -649,7 +645,7 @@
                 Map<UseCase, ConfigPair> configs = getConfigs(Arrays.asList(useCases),
                         mCameraConfig.getUseCaseConfigFactory(), mUseCaseConfigFactory);
                 calculateSuggestedResolutions(mCameraInternal.getCameraInfoInternal(),
-                        Arrays.asList(useCases), Collections.emptyList(), configs);
+                        Arrays.asList(useCases), emptyList(), configs);
             } catch (IllegalArgumentException e) {
                 return false;
             }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
deleted file mode 100644
index de2b346..0000000
--- a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * 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 androidx.camera.core
-
-import android.os.Build
-import androidx.camera.core.SurfaceProcessor.PREVIEW
-import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.testing.fakes.FakeSurfaceProcessor
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.internal.DoNotInstrument
-
-/**
- * Unit tests for [EffectBundle].
- */
-@RunWith(RobolectricTestRunner::class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class EffectBundleTest {
-
-    @Test(expected = IllegalArgumentException::class)
-    fun noEffect_throwsException() {
-        EffectBundle.Builder(mainThreadExecutor()).build()
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun addMoreThanOnePreviewEffect_throwsException() {
-        val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
-        EffectBundle.Builder(mainThreadExecutor())
-            .addEffect(PREVIEW, surfaceProcessor)
-            .addEffect(PREVIEW, surfaceProcessor)
-    }
-
-    @Test
-    fun addPreviewEffect_hasPreviewEffect() {
-        // Arrange.
-        val surfaceProcessor =
-            FakeSurfaceProcessor(mainThreadExecutor())
-        // Act.
-        val effectBundle = EffectBundle.Builder(mainThreadExecutor())
-            .addEffect(PREVIEW, surfaceProcessor)
-            .build()
-        // Assert.
-        assertThat(effectBundle.effects.values.first() as SurfaceProcessor).isEqualTo(
-            surfaceProcessor
-        )
-    }
-}
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index bd9048e..0444977 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -22,10 +22,10 @@
 import android.util.Rational
 import android.util.Size
 import android.view.Surface
-import androidx.camera.core.EffectBundle
+import androidx.camera.core.CameraEffect
+import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.UseCase
 import androidx.camera.core.ViewPort
 import androidx.camera.core.impl.CameraConfig
@@ -70,7 +70,7 @@
 class CameraUseCaseAdapterTest {
 
     private lateinit var surfaceProcessor: FakeSurfaceProcessor
-    private lateinit var effectBundle: EffectBundle
+    private lateinit var effects: List<CameraEffect>
     private lateinit var executor: ExecutorService
 
     private lateinit var fakeCameraDeviceSurfaceManager: FakeCameraDeviceSurfaceManager
@@ -86,7 +86,9 @@
         fakeCameraSet.add(fakeCamera)
         surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
         executor = Executors.newSingleThreadExecutor()
-        effectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceProcessor).build()
+        effects = listOf(
+            CameraEffect.Builder(PREVIEW).setSurfaceProcessor(executor, surfaceProcessor).build()
+        )
     }
 
     @After
@@ -339,8 +341,8 @@
         assertThat(fakeUseCase.sensorToBufferTransformMatrix).isEqualTo(Matrix().apply {
             // From 4032x3024 to 4032x3022 with Crop Inside, no scale and Y shift 1.
             setValues(floatArrayOf(/*scaleX=*/1f, 0f, /*translateX=*/0f,
-                                   0f, /*scaleY=*/1f, /*translateY=*/-1f,
-                                   0f, 0f, 1f))
+                0f, /*scaleY=*/1f, /*translateY=*/-1f,
+                0f, 0f, 1f))
         })
     }
 
@@ -617,14 +619,14 @@
     fun updateEffects_effectsAddedAndRemoved() {
         // Arrange.
         val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-        // Act: update use cases with effects bundle
-        CameraUseCaseAdapter.updateEffects(effectBundle, listOf(preview))
+        // Act: update use cases with effects.
+        CameraUseCaseAdapter.updateEffects(effects, listOf(preview))
         // Assert: preview has processor wrapped with the right executor.
         val previewProcessor = preview.processor as SurfaceProcessorWithExecutor
         assertThat(previewProcessor.processor).isEqualTo(surfaceProcessor)
         assertThat(previewProcessor.executor).isEqualTo(executor)
-        // Act: update again with null effects bundle
-        CameraUseCaseAdapter.updateEffects(null, listOf(preview))
+        // Act: update again with no effects.
+        CameraUseCaseAdapter.updateEffects(listOf(), listOf(preview))
         // Assert: preview no longer has processors.
         assertThat(preview.processor).isNull()
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
index a409ec67..64bebab 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
@@ -24,9 +24,9 @@
 import android.os.Looper.getMainLooper
 import android.util.Size
 import android.view.Surface
+import androidx.camera.core.CameraEffect
 import androidx.camera.core.SurfaceOutput
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
-import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.SurfaceRequest.Result.RESULT_REQUEST_CANCELLED
 import androidx.camera.core.SurfaceRequest.TransformationInfo
@@ -70,7 +70,7 @@
     @Before
     fun setUp() {
         settableSurface = SettableSurface(
-            SurfaceProcessor.PREVIEW, Size(640, 480), ImageFormat.PRIVATE,
+            CameraEffect.PREVIEW, Size(640, 480), ImageFormat.PRIVATE,
             Matrix(), true, Rect(), 0, false
         )
         fakeSurfaceTexture = SurfaceTexture(0)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
index e6d0b85..621eb1d 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
@@ -22,9 +22,9 @@
 import android.os.Looper
 import android.util.Size
 import android.view.Surface
+import androidx.camera.core.CameraEffect
 import androidx.camera.core.SurfaceOutput.GlTransformOptions
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
-import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.impl.utils.TransformUtils.sizeToRect
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import com.google.common.truth.Truth.assertThat
@@ -46,7 +46,7 @@
 class SurfaceOutputImplTest {
 
     companion object {
-        private const val TARGET = SurfaceProcessor.PREVIEW
+        private const val TARGET = CameraEffect.PREVIEW
         private const val FORMAT = PixelFormat.RGBA_8888
         private val OUTPUT_SIZE = Size(640, 480)
         private val INPUT_SIZE = Size(640, 480)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
index abbbda5..0724a8d 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
@@ -23,10 +23,10 @@
 import android.os.Looper.getMainLooper
 import android.util.Size
 import android.view.Surface
+import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.SurfaceOutput.GlTransformOptions
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.APPLY_CROP_ROTATE_AND_MIRRORING
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
-import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.SurfaceRequest.TransformationInfo
 import androidx.camera.core.impl.utils.TransformUtils.is90or270
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
index 65416c3..972d36c 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static java.util.Collections.emptyList;
+
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.testing.fakes.FakeCamera;
@@ -116,7 +118,7 @@
     public void lifecycleCameraIsNotActive_bindUseCase_whenLifecycleIsNotStarted() {
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         // LifecycleCamera is inactive before the lifecycle state becomes ON_START.
         assertThat(lifecycleCamera.isActive()).isFalse();
@@ -126,7 +128,7 @@
     public void lifecycleCameraIsActive_lifecycleStartedAfterBindUseCase() {
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         mLifecycle.start();
         // LifecycleCamera is active after the lifecycle state becomes ON_START.
@@ -138,7 +140,7 @@
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
         mLifecycle.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // LifecycleCamera is active after binding a use case when lifecycle state is ON_START.
@@ -150,13 +152,13 @@
         // Creates first LifecycleCamera with use case bound.
         LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Creates second LifecycleCamera with use case bound to the same Lifecycle.
         LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(mLifecycle,
                 createNewCameraUseCaseAdapter());
-        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
     }
 
@@ -167,7 +169,7 @@
                 mCameraUseCaseAdapter);
         mLifecycle.start();
         FakeUseCase useCase = new FakeUseCase();
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(useCase));
 
         // Unbinds the use case that was bound previously.
@@ -186,7 +188,7 @@
         mLifecycle.start();
         FakeUseCase useCase0 = new FakeUseCase();
         FakeUseCase useCase1 = new FakeUseCase();
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Arrays.asList(useCase0, useCase1));
 
         // Only unbinds one use case but another one is kept in the LifecycleCamera.
@@ -203,7 +205,7 @@
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
         mLifecycle.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Unbinds all use cases from all LifecycleCamera by the unbindAll() API.
@@ -219,7 +221,7 @@
         LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
         mLifecycle.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Starts second lifecycle with use case bound.
@@ -227,7 +229,7 @@
         LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(lifecycle1,
                 createNewCameraUseCaseAdapter());
         lifecycle1.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // The previous LifecycleCamera becomes inactive after new LifecycleCamera becomes active.
@@ -242,7 +244,7 @@
         LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
         mLifecycle.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Starts second lifecycle with use case bound.
@@ -250,11 +252,11 @@
         LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(lifecycle1,
                 createNewCameraUseCaseAdapter());
         lifecycle1.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Binds new use case to the next most recent active LifecycleCamera.
-        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // The next most recent active LifecycleCamera becomes active after binding new use case.
@@ -270,7 +272,7 @@
         LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(mLifecycle,
                 mCameraUseCaseAdapter);
         mLifecycle.start();
-        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Starts second lifecycle with use case bound.
@@ -279,7 +281,7 @@
                 createNewCameraUseCaseAdapter());
         lifecycle1.start();
         FakeUseCase useCase = new FakeUseCase();
-        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(),
                 Collections.singletonList(useCase));
 
         // Unbinds use case from the most recent active LifecycleCamera.
@@ -298,7 +300,7 @@
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
         FakeUseCase useCase = new FakeUseCase();
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(useCase));
 
         assertThat(useCase.isDetached()).isFalse();
@@ -321,7 +323,7 @@
         // Starts first lifecycle and check LifecycleCamera active state is true.
         LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         mLifecycle.start();
         assertThat(firstLifecycleCamera.isActive()).isTrue();
@@ -330,7 +332,7 @@
         FakeLifecycleOwner secondLifecycle = new FakeLifecycleOwner();
         LifecycleCamera secondLifecycleCamera = mRepository.createLifecycleCamera(secondLifecycle,
                 createNewCameraUseCaseAdapter());
-        mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         secondLifecycle.start();
         assertThat(secondLifecycleCamera.isActive()).isTrue();
@@ -342,7 +344,7 @@
         // Starts first lifecycle and check LifecycleCamera active state is true.
         LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         mLifecycle.start();
         assertThat(firstLifecycleCamera.isActive()).isTrue();
@@ -351,7 +353,7 @@
         FakeLifecycleOwner secondLifecycle = new FakeLifecycleOwner();
         LifecycleCamera secondLifecycleCamera = mRepository.createLifecycleCamera(secondLifecycle,
                 createNewCameraUseCaseAdapter());
-        mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         secondLifecycle.start();
         assertThat(secondLifecycleCamera.isActive()).isTrue();
@@ -368,7 +370,7 @@
         // Starts first LifecycleCamera with use case bound.
         LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         mLifecycle.start();
         assertThat(firstLifecycleCamera.isActive()).isTrue();
@@ -395,7 +397,7 @@
         // Starts second LifecycleCamera with use case bound to the same Lifecycle.
         LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(
                 mLifecycle, createNewCameraUseCaseAdapter());
-        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
 
         // Starts third LifecycleCamera with no use case bound to the same Lifecycle.
@@ -450,7 +452,7 @@
         // Starts LifecycleCamera with use case bound.
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
-        mRepository.bindToLifecycleCamera(lifecycleCamera, null, null,
+        mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(),
                 Collections.singletonList(new FakeUseCase()));
         mLifecycle.start();
         assertThat(lifecycleCamera.isActive()).isTrue();
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index 0bb2cbe..2f7d239 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -22,11 +22,11 @@
 import android.content.pm.PackageManager
 import androidx.annotation.OptIn
 import androidx.annotation.RequiresApi
+import androidx.camera.core.CameraEffect
+import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraXConfig
-import androidx.camera.core.EffectBundle
 import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.UseCaseGroup
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -80,11 +80,10 @@
         // Arrange.
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
         val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
-        val effectBundle =
-            EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceProcessor).build()
+        val effect = CameraEffect.Builder(PREVIEW)
+            .setSurfaceProcessor(mainThreadExecutor(), surfaceProcessor).build()
         val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-        val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview)
-            .setEffectBundle(effectBundle).build()
+        val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview).addEffect(effect).build()
 
         runBlocking(MainScope().coroutineContext) {
             // Act.
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
index 8fc8a6b..706c842 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
@@ -20,7 +20,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.camera.core.EffectBundle;
+import androidx.camera.core.CameraEffect;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.impl.CameraInternal;
@@ -39,6 +39,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -250,14 +251,14 @@
      *
      * @param lifecycleCamera The LifecycleCamera which the use cases will be bound to.
      * @param viewPort The viewport which represents the visible camera sensor rect.
-     * @param effectBundle The effects applied to the camera outputs.
+     * @param effects The effects applied to the camera outputs.
      * @param useCases The use cases to bind to a lifecycle.
      * @throws IllegalArgumentException If multiple LifecycleCameras with use cases are
      * registered to the same LifecycleOwner. Or all use cases will exceed the capability of the
      * camera after binding them to the LifecycleCamera.
      */
     void bindToLifecycleCamera(@NonNull LifecycleCamera lifecycleCamera,
-            @Nullable ViewPort viewPort, @Nullable EffectBundle effectBundle,
+            @Nullable ViewPort viewPort, @NonNull List<CameraEffect> effects,
             @NonNull Collection<UseCase> useCases) {
         synchronized (mLock) {
             Preconditions.checkArgument(!useCases.isEmpty());
@@ -278,7 +279,7 @@
 
             try {
                 lifecycleCamera.getCameraUseCaseAdapter().setViewPort(viewPort);
-                lifecycleCamera.getCameraUseCaseAdapter().setEffectBundle(effectBundle);
+                lifecycleCamera.getCameraUseCaseAdapter().setEffects(effects);
                 lifecycleCamera.bind(useCases);
             } catch (CameraUseCaseAdapter.CameraException e) {
                 throw new IllegalArgumentException(e.getMessage());
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index 066a237..33dabca 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.lifecycle;
 
+import static java.util.Collections.emptyList;
+
 import android.app.Application;
 import android.content.Context;
 import android.os.Handler;
@@ -28,13 +30,13 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.camera.core.Camera;
+import androidx.camera.core.CameraEffect;
 import androidx.camera.core.CameraFilter;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraX;
 import androidx.camera.core.CameraXConfig;
-import androidx.camera.core.EffectBundle;
 import androidx.camera.core.ImageAnalysis;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.InitializationException;
@@ -358,7 +360,7 @@
     public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
             @NonNull CameraSelector cameraSelector,
             @NonNull UseCase... useCases) {
-        return bindToLifecycle(lifecycleOwner, cameraSelector, null, null, useCases);
+        return bindToLifecycle(lifecycleOwner, cameraSelector, null, emptyList(), useCases);
     }
 
     /**
@@ -380,7 +382,7 @@
             @NonNull CameraSelector cameraSelector,
             @NonNull UseCaseGroup useCaseGroup) {
         return bindToLifecycle(lifecycleOwner, cameraSelector,
-                useCaseGroup.getViewPort(), useCaseGroup.getEffectBundle(),
+                useCaseGroup.getViewPort(), useCaseGroup.getEffects(),
                 useCaseGroup.getUseCases().toArray(new UseCase[0]));
     }
 
@@ -433,7 +435,7 @@
      * @param cameraSelector The camera selector which determines the camera to use for set of
      *                       use cases.
      * @param viewPort       The viewPort which represents the visible camera sensor rect.
-     * @param effectBundle   The effects applied to the camera outputs.
+     * @param effects        The effects applied to the camera outputs.
      * @param useCases       The use cases to bind to a lifecycle.
      * @return The {@link Camera} instance which is determined by the camera selector and
      * internal requirements.
@@ -448,7 +450,7 @@
             @NonNull LifecycleOwner lifecycleOwner,
             @NonNull CameraSelector cameraSelector,
             @Nullable ViewPort viewPort,
-            @Nullable EffectBundle effectBundle,
+            @NonNull List<CameraEffect> effects,
             @NonNull UseCase... useCases) {
         Threads.checkMainThread();
         // TODO(b/153096869): override UseCase's target rotation.
@@ -534,7 +536,7 @@
         }
 
         mLifecycleCameraRepository.bindToLifecycleCamera(lifecycleCameraToBind, viewPort,
-                effectBundle, Arrays.asList(useCases));
+                effects, Arrays.asList(useCases));
 
         return lifecycleCameraToBind;
     }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index b23e20e..94d1746 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -16,6 +16,7 @@
 
 package androidx.camera.video;
 
+import static androidx.camera.core.CameraEffect.VIDEO_CAPTURE;
 import static androidx.camera.core.SurfaceOutput.GlTransformOptions.APPLY_CROP_ROTATE_AND_MIRRORING;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_DEFAULT_RESOLUTION;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_MAX_RESOLUTION;
@@ -65,7 +66,6 @@
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
@@ -501,7 +501,7 @@
                             VideoCapabilities.from(camera.getCameraInfo()), timebase, mediaSpec,
                             resolution, targetFpsRange));
             SettableSurface cameraSurface = new SettableSurface(
-                    SurfaceProcessor.VIDEO_CAPTURE,
+                    VIDEO_CAPTURE,
                     resolution,
                     ImageFormat.PRIVATE,
                     getSensorToBufferTransformMatrix(),
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
index 8abbab7..e61af75 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
@@ -22,13 +22,13 @@
 import android.view.View
 import androidx.camera.camera2.Camera2Config
 import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.CameraEffect
+import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraSelector.LENS_FACING_BACK
 import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
 import androidx.camera.core.CameraXConfig
-import androidx.camera.core.EffectBundle
 import androidx.camera.core.ImageCapture
-import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraPipeConfigTestRule
@@ -127,26 +127,17 @@
         }
         waitUtilPreviewViewIsReady(previewView!!)
 
-        // Act: set an EffectBundle
-        instrumentation.runOnMainSync {
-            controller!!.setEffectBundle(
-                EffectBundle.Builder(mainThreadExecutor())
-                    .addEffect(PREVIEW,
-                        FakeSurfaceProcessor(
-                            mainThreadExecutor()
-                        )
-                    )
-                    .build()
-            )
-        }
+        // Act: set an effect
+        val effect = CameraEffect.Builder(PREVIEW).setSurfaceProcessor(
+            mainThreadExecutor(), FakeSurfaceProcessor(mainThreadExecutor())
+        ).build()
+        instrumentation.runOnMainSync { controller!!.setEffects(listOf(effect)) }
 
         // Assert: preview has effect
         assertThat(controller!!.mPreview.processor).isNotNull()
 
-        // Act: clear the EffectBundle
-        instrumentation.runOnMainSync {
-            controller!!.setEffectBundle(null)
-        }
+        // Act: clear the effects
+        instrumentation.runOnMainSync { controller!!.setEffects(listOf()) }
 
         // Assert: preview no longer has the effect.
         assertThat(controller!!.mPreview.processor).isNull()
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index a0f314e..711938c 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -20,6 +20,8 @@
 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
 import static androidx.camera.view.CameraController.OutputSize.UNASSIGNED_ASPECT_RATIO;
 
+import static java.util.Collections.emptyList;
+
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Matrix;
@@ -41,11 +43,11 @@
 import androidx.camera.core.AspectRatio;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraControl;
+import androidx.camera.core.CameraEffect;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraUnavailableException;
-import androidx.camera.core.EffectBundle;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.FocusMeteringResult;
 import androidx.camera.core.ImageAnalysis;
@@ -80,6 +82,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -306,8 +309,8 @@
     final MutableLiveData<Integer> mTapToFocusState = new MutableLiveData<>(
             TAP_TO_FOCUS_NOT_STARTED);
 
-    @Nullable
-    private EffectBundle mEffectBundle;
+    @NonNull
+    private List<CameraEffect> mEffects = emptyList();
 
     private final Context mAppContext;
 
@@ -1664,13 +1667,13 @@
     /**
      * Sets post-processing effects.
      *
-     * @param effectBundle the effects applied to camera output.
+     * @param effects the effects applied to camera output.
      * @hide
-     * @see UseCaseGroup.Builder#getEffectBundle()
+     * @see UseCaseGroup.Builder#addEffect
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public void setEffectBundle(@Nullable EffectBundle effectBundle) {
-        if (mEffectBundle == effectBundle) {
+    public void setEffects(@NonNull List<CameraEffect> effects) {
+        if (Objects.equals(mEffects, effects)) {
             // Same effect. No change needed.
             return;
         }
@@ -1678,7 +1681,7 @@
             // Unbind to make sure the pipelines will be recreated.
             mCameraProvider.unbindAll();
         }
-        mEffectBundle = effectBundle;
+        mEffects = effects;
         startCameraAndTrackStates();
     }
 
@@ -1763,8 +1766,8 @@
         }
 
         builder.setViewPort(mViewPort);
-        if (mEffectBundle != null) {
-            builder.setEffectBundle(mEffectBundle);
+        for (CameraEffect effect : mEffects) {
+            builder.addEffect(effect);
         }
         return builder.build();
     }
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index f949eb5..0f90843 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -134,7 +134,9 @@
     fun enableEffect_effectIsEnabled() {
         // Arrange: launch app and verify effect is inactive.
         fragment.assertPreviewIsStreaming()
-        assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isFalse()
+        val processor =
+            fragment.mToneMappingPreviewEffect.surfaceProcessor as ToneMappingSurfaceProcessor
+        assertThat(processor.isSurfaceRequestedAndProvided()).isFalse()
 
         // Act: turn on effect.
         val effectToggleId = "androidx.camera.integration.view:id/effect_toggle"
@@ -142,7 +144,7 @@
         instrumentation.waitForIdleSync()
 
         // Assert: verify that effect is active.
-        assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isTrue()
+        assertThat(processor.isSurfaceRequestedAndProvided()).isTrue()
     }
 
     @Test
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index 97a9bde..b7c539c 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -19,6 +19,9 @@
 import static androidx.camera.core.impl.utils.TransformUtils.getRectToRect;
 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+
 import android.annotation.SuppressLint;
 import android.app.Dialog;
 import android.content.ContentResolver;
@@ -54,13 +57,11 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.CameraSelector;
-import androidx.camera.core.EffectBundle;
 import androidx.camera.core.ImageAnalysis;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.ZoomState;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
@@ -126,7 +127,7 @@
     private ImageAnalysis.Analyzer mWrappedAnalyzer;
 
     @VisibleForTesting
-    ToneMappingSurfaceProcessor mSurfaceProcessor;
+    ToneMappingPreviewEffect mToneMappingPreviewEffect;
 
     private final ImageAnalysis.Analyzer mAnalyzer = image -> {
         byte[] bytes = new byte[image.getPlanes()[0].getBuffer().remaining()];
@@ -182,7 +183,7 @@
         });
 
         // Set up post-processing effects.
-        mSurfaceProcessor = new ToneMappingSurfaceProcessor();
+        mToneMappingPreviewEffect = new ToneMappingPreviewEffect();
         mEffectToggle = view.findViewById(R.id.effect_toggle);
         mEffectToggle.setOnCheckedChangeListener((compoundButton, isChecked) -> onEffectsToggled());
         onEffectsToggled();
@@ -352,16 +353,14 @@
             mExecutorService.shutdown();
         }
         mRotationProvider.removeListener(mRotationListener);
-        mSurfaceProcessor.release();
+        mToneMappingPreviewEffect.release();
     }
 
     private void onEffectsToggled() {
         if (mEffectToggle.isChecked()) {
-            mCameraController.setEffectBundle(new EffectBundle.Builder(mainThreadExecutor())
-                    .addEffect(SurfaceProcessor.PREVIEW, mSurfaceProcessor)
-                    .build());
-        } else if (mSurfaceProcessor != null) {
-            mCameraController.setEffectBundle(null);
+            mCameraController.setEffects(singletonList(mToneMappingPreviewEffect));
+        } else {
+            mCameraController.setEffects(emptyList());
         }
     }
 
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt
new file mode 100644
index 0000000..25bdc1c
--- /dev/null
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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 androidx.camera.integration.view
+
+import androidx.camera.core.CameraEffect
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+
+/**
+ * A tone mapping effect for preview UseCase.
+ */
+internal class ToneMappingPreviewEffect :
+    CameraEffect(PREVIEW, mainThreadExecutor(), ToneMappingSurfaceProcessor()) {
+
+    fun release() {
+        (surfaceProcessor as? ToneMappingSurfaceProcessor)?.release()
+    }
+}
\ No newline at end of file