1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.core;
18 
19 import static androidx.camera.core.CameraEffect.IMAGE_CAPTURE;
20 import static androidx.camera.core.CameraEffect.PREVIEW;
21 import static androidx.camera.core.CameraEffect.VIDEO_CAPTURE;
22 import static androidx.camera.core.impl.StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
23 import static androidx.camera.core.processing.TargetUtils.checkSupportedTargets;
24 import static androidx.camera.core.processing.TargetUtils.getHumanReadableName;
25 import static androidx.core.util.Preconditions.checkArgument;
26 
27 import android.util.Range;
28 
29 import androidx.annotation.RestrictTo;
30 import androidx.lifecycle.Lifecycle;
31 
32 import org.jspecify.annotations.NonNull;
33 import org.jspecify.annotations.Nullable;
34 
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Locale;
39 
40 /**
41  * Represents a collection of {@link UseCase}.
42  *
43  * <p>When the {@link UseCaseGroup} is bound to {@link Lifecycle}, it binds all the
44  * {@link UseCase}s to the same {@link Lifecycle}. {@link UseCase}s inside of a
45  * {@link UseCaseGroup} usually share some common properties like the FOV defined by
46  * {@link ViewPort}.
47  */
48 public final class UseCaseGroup {
49     private final @Nullable ViewPort mViewPort;
50     private final @NonNull List<UseCase> mUseCases;
51     private final @NonNull List<CameraEffect> mEffects;
52     private final @NonNull Range<Integer> mTargetHighSpeedFrameRate;
53 
UseCaseGroup(@ullable ViewPort viewPort, @NonNull List<UseCase> useCases, @NonNull List<CameraEffect> effects)54     UseCaseGroup(@Nullable ViewPort viewPort, @NonNull List<UseCase> useCases,
55             @NonNull List<CameraEffect> effects) {
56         mViewPort = viewPort;
57         mUseCases = useCases;
58         mEffects = effects;
59         mTargetHighSpeedFrameRate = FRAME_RATE_RANGE_UNSPECIFIED;
60     }
61 
62     /**
63      * Gets the {@link ViewPort} shared by the {@link UseCase} collection.
64      */
getViewPort()65     public @Nullable ViewPort getViewPort() {
66         return mViewPort;
67     }
68 
69     /**
70      * Gets the {@link UseCase}s.
71      */
getUseCases()72     public @NonNull List<UseCase> getUseCases() {
73         return mUseCases;
74     }
75 
76     /**
77      * Gets the {@link CameraEffect}s.
78      */
getEffects()79     public @NonNull List<CameraEffect> getEffects() {
80         return mEffects;
81     }
82 
83     /**
84      * Gets the target high speed frame rate.
85      */
86     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
getTargetHighSpeedFrameRate()87     public @NonNull Range<Integer> getTargetHighSpeedFrameRate() {
88         return mTargetHighSpeedFrameRate;
89     }
90 
91     /**
92      * A builder for generating {@link UseCaseGroup}.
93      */
94     public static final class Builder {
95 
96         // Allow-list effect targets supported by CameraX.
97         private static final List<Integer> SUPPORTED_TARGETS = Arrays.asList(
98                 PREVIEW,
99                 VIDEO_CAPTURE,
100                 IMAGE_CAPTURE,
101                 PREVIEW | VIDEO_CAPTURE,
102                 PREVIEW | VIDEO_CAPTURE | IMAGE_CAPTURE);
103 
104         private ViewPort mViewPort;
105         private final List<UseCase> mUseCases;
106         private final List<CameraEffect> mEffects;
107 
Builder()108         public Builder() {
109             mUseCases = new ArrayList<>();
110             mEffects = new ArrayList<>();
111         }
112 
113         /**
114          * Sets {@link ViewPort} shared by the {@link UseCase}s.
115          */
setViewPort(@onNull ViewPort viewPort)116         public @NonNull Builder setViewPort(@NonNull ViewPort viewPort) {
117             mViewPort = viewPort;
118             return this;
119         }
120 
121         /**
122          * Adds a {@link CameraEffect} to the collection.
123          *
124          * <p>The value of {@link CameraEffect#getTargets()} must be one of the supported values
125          * below:
126          * <ul>
127          * <li>{@link CameraEffect#PREVIEW}
128          * <li>{@link CameraEffect#VIDEO_CAPTURE}
129          * <li>{@link CameraEffect#IMAGE_CAPTURE}
130          * <li>{@link CameraEffect#VIDEO_CAPTURE} | {@link CameraEffect#PREVIEW}
131          * <li>{@link CameraEffect#VIDEO_CAPTURE} | {@link CameraEffect#PREVIEW} |
132          * {@link CameraEffect#IMAGE_CAPTURE}
133          * </ul>
134          *
135          * <p>The targets must be mutually exclusive of each other, otherwise, the {@link #build()}
136          * method will throw {@link IllegalArgumentException}. For example, it's invalid to have
137          * one {@link CameraEffect} with target {@link CameraEffect#PREVIEW} and another
138          * {@link CameraEffect} with target {@link CameraEffect#PREVIEW} |
139          * {@link CameraEffect#VIDEO_CAPTURE}, since they both target {@link Preview}.
140          *
141          * <p>Once added, CameraX will use the {@link CameraEffect}s to process the outputs of
142          * the {@link UseCase}s.
143          */
addEffect(@onNull CameraEffect cameraEffect)144         public @NonNull Builder addEffect(@NonNull CameraEffect cameraEffect) {
145             mEffects.add(cameraEffect);
146             return this;
147         }
148 
149         /**
150          * Checks effect targets and throw {@link IllegalArgumentException}.
151          *
152          * <p>Throws exception if the effects 1) contains conflicting targets or 2) contains
153          * effects that is not in the allowlist.
154          */
checkEffectTargets()155         private void checkEffectTargets() {
156             int existingTargets = 0;
157             for (CameraEffect effect : mEffects) {
158                 int targets = effect.getTargets();
159                 checkSupportedTargets(SUPPORTED_TARGETS, targets);
160                 int overlappingTargets = existingTargets & targets;
161                 if (overlappingTargets > 0) {
162                     throw new IllegalArgumentException(String.format(Locale.US,
163                             "More than one effects has targets %s.",
164                             getHumanReadableName(overlappingTargets)));
165                 }
166                 existingTargets |= targets;
167             }
168         }
169 
170 
171         /**
172          * Adds {@link UseCase} to the collection.
173          */
addUseCase(@onNull UseCase useCase)174         public @NonNull Builder addUseCase(@NonNull UseCase useCase) {
175             mUseCases.add(useCase);
176             return this;
177         }
178 
179         /**
180          * Builds a {@link UseCaseGroup} from the current state.
181          */
build()182         public @NonNull UseCaseGroup build() {
183             checkArgument(!mUseCases.isEmpty(), "UseCase must not be empty.");
184             checkEffectTargets();
185             return new UseCaseGroup(mViewPort, mUseCases, mEffects);
186         }
187     }
188 }
189