1 /* 2 * Copyright 2023 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.view; 18 19 import static androidx.camera.core.ImageCapture.FLASH_MODE_SCREEN; 20 import static androidx.camera.core.impl.utils.Threads.checkMainThread; 21 22 import android.animation.Animator; 23 import android.animation.ValueAnimator; 24 import android.app.Activity; 25 import android.content.Context; 26 import android.graphics.Color; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.view.Window; 30 import android.view.WindowManager; 31 32 import androidx.annotation.RestrictTo; 33 import androidx.annotation.UiThread; 34 import androidx.camera.core.ImageCapture; 35 import androidx.camera.core.ImageCapture.ScreenFlash; 36 import androidx.camera.core.Logger; 37 import androidx.camera.view.internal.ScreenFlashUiInfo; 38 import androidx.fragment.app.Fragment; 39 40 import org.jspecify.annotations.NonNull; 41 import org.jspecify.annotations.Nullable; 42 43 /** 44 * Custom View that implements a basic UI for screen flash photo capture. 45 * 46 * <p> This class provides an {@link ScreenFlash} implementation with 47 * {@link #getScreenFlash()} for the 48 * {@link ImageCapture#setScreenFlash(ImageCapture.ScreenFlash)} API. If a 49 * {@link CameraController} is used for CameraX operations,{@link #setController(CameraController)} 50 * should be used to set the controller to this view. Normally, this view is kept fully 51 * transparent. It becomes fully visible for the duration of screen flash photo capture. The 52 * screen brightness is also maximized for that duration. 53 * 54 * <p> The default color of the view is {@link Color#WHITE}, but it can be changed with 55 * {@link View#setBackgroundColor(int)} API. The elevation of this view is always set to 56 * {@link Float#MAX_VALUE} so that it always appears on top in its view hierarchy during screen 57 * flash. 58 * 59 * <p> This view is also used internally in {@link PreviewView}, so may not be required if user 60 * is already using {@link PreviewView}. However, note that the internal instance of 61 * {@link PreviewView} has the same dimensions as {@link PreviewView}. So if the 62 * {@link PreviewView} does not encompass the full screen, users may want to use this view 63 * separately so that whole screen can be encompassed during screen flash operation. 64 * 65 * @see #getScreenFlash 66 * @see ImageCapture#FLASH_MODE_SCREEN 67 */ 68 public final class ScreenFlashView extends View { 69 private static final String TAG = "ScreenFlashView"; 70 private CameraController mCameraController; 71 private Window mScreenFlashWindow; 72 private ImageCapture.ScreenFlash mScreenFlash; 73 74 /** The timeout in seconds for the visibility animation at {@link ScreenFlash#apply}. */ 75 private static final long ANIMATION_DURATION_MILLIS = 1000; 76 77 @UiThread ScreenFlashView(@onNull Context context)78 public ScreenFlashView(@NonNull Context context) { 79 this(context, null); 80 } 81 82 @UiThread ScreenFlashView(@onNull Context context, @Nullable AttributeSet attrs)83 public ScreenFlashView(@NonNull Context context, @Nullable AttributeSet attrs) { 84 this(context, attrs, 0); 85 } 86 87 @UiThread ScreenFlashView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)88 public ScreenFlashView(@NonNull Context context, @Nullable AttributeSet attrs, 89 int defStyleAttr) { 90 this(context, attrs, defStyleAttr, 0); 91 } 92 93 @UiThread ScreenFlashView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)94 public ScreenFlashView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, 95 int defStyleRes) { 96 super(context, attrs, defStyleAttr, defStyleRes); 97 98 setBackgroundColor(Color.WHITE); 99 setAlpha(0f); 100 setElevation(Float.MAX_VALUE); 101 } 102 103 /** 104 * Sets the {@link CameraController}. 105 * 106 * <p> Once set, the controller will use the {@code ScreenFlashView} for screen flash related UI 107 * operations. 108 * 109 * @throws IllegalStateException If {@link ImageCapture#FLASH_MODE_SCREEN} is set to the 110 * {@link CameraController}, but a non-null {@link Window} 111 * instance has not been set with {@link #setScreenFlashWindow}. 112 * @see CameraController 113 */ 114 @UiThread setController(@ullable CameraController cameraController)115 public void setController(@Nullable CameraController cameraController) { 116 checkMainThread(); 117 118 if (mCameraController != null && mCameraController != cameraController) { 119 // If already bound to a different controller, remove the ScreenFlash instance from the 120 // old controller. 121 setScreenFlashUiInfo(null); 122 } 123 mCameraController = cameraController; 124 125 if (cameraController == null) { 126 return; 127 } 128 129 if (cameraController.getImageCaptureFlashMode() == FLASH_MODE_SCREEN 130 && mScreenFlashWindow == null) { 131 throw new IllegalStateException( 132 "No window set despite setting FLASH_MODE_SCREEN in CameraController"); 133 } 134 135 setScreenFlashUiInfo(getScreenFlash()); 136 } 137 setScreenFlashUiInfo(ImageCapture.ScreenFlash control)138 private void setScreenFlashUiInfo(ImageCapture.ScreenFlash control) { 139 if (mCameraController == null) { 140 Logger.d(TAG, "setScreenFlashUiInfo: mCameraController is null!"); 141 return; 142 } 143 mCameraController.setScreenFlashUiInfo(new ScreenFlashUiInfo( 144 ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW, control)); 145 } 146 147 /** 148 * Sets a {@link Window} instance for subsequent photo capture requests with 149 * {@link ImageCapture} use case when {@link ImageCapture#FLASH_MODE_SCREEN} is set. 150 * 151 * <p>The calling of this API will take effect for {@code ImageCapture#FLASH_MODE_SCREEN} only 152 * and the {@code Window} will be ignored for other flash modes. During screen flash photo 153 * capture, the window is used for the purpose of changing screen brightness. 154 * 155 * <p> If the implementation provided by the user is no longer valid (e.g. due to any 156 * {@link android.app.Activity} or {@link android.view.View} reference used in the 157 * implementation becoming invalid), user needs to re-set a new valid window or clear the 158 * previous one with {@code setScreenFlashWindow(null)}, whichever appropriate. 159 * 160 * <p>For most app scenarios, a {@code Window} instance can be obtained from 161 * {@link Activity#getWindow()}. In case of a fragment, {@link Fragment#getActivity()} can 162 * first be used to get the activity instance. 163 * 164 * @param screenFlashWindow A {@link Window} instance that is used to change the brightness 165 * during screen flash photo capture. 166 */ 167 @UiThread setScreenFlashWindow(@ullable Window screenFlashWindow)168 public void setScreenFlashWindow(@Nullable Window screenFlashWindow) { 169 checkMainThread(); 170 updateScreenFlash(screenFlashWindow); 171 mScreenFlashWindow = screenFlashWindow; 172 setScreenFlashUiInfo(getScreenFlash()); 173 } 174 175 /** Update {@link #mScreenFlash} if required. */ updateScreenFlash(Window window)176 private void updateScreenFlash(Window window) { 177 Logger.d(TAG, "updateScreenFlash: is new window null = " + (window == null) 178 + ", is new window same as previous = " + (window == mScreenFlashWindow)); 179 180 if (mScreenFlashWindow != window) { 181 mScreenFlash = window == null ? null : new ScreenFlash() { 182 private float mPreviousBrightness; 183 private ValueAnimator mAnimator; 184 185 @Override 186 public void apply(long expirationTimeMillis, 187 ImageCapture.@NonNull ScreenFlashListener screenFlashListener) { 188 Logger.d(TAG, "ScreenFlash#apply"); 189 190 mPreviousBrightness = getBrightness(); 191 setBrightness(1.0f); 192 193 if (mAnimator != null) { 194 mAnimator.cancel(); 195 } 196 mAnimator = animateToFullOpacity(screenFlashListener::onCompleted); 197 } 198 199 @Override 200 public void clear() { 201 Logger.d(TAG, "ScreenFlash#clear"); 202 203 if (mAnimator != null) { 204 mAnimator.cancel(); 205 mAnimator = null; 206 } 207 208 setAlpha(0f); 209 210 // Restore screen brightness 211 setBrightness(mPreviousBrightness); 212 } 213 }; 214 } 215 } 216 animateToFullOpacity(@ullable Runnable onAnimationEnd)217 private ValueAnimator animateToFullOpacity(@Nullable Runnable onAnimationEnd) { 218 Logger.d(TAG, "animateToFullOpacity"); 219 220 ValueAnimator animator = ValueAnimator.ofFloat(0F, 1F); 221 222 // TODO: b/355168952 - Allow users to overwrite the animation duration. 223 animator.setDuration(getVisibilityRampUpAnimationDurationMillis()); 224 225 animator.addUpdateListener(animation -> { 226 Logger.d(TAG, "animateToFullOpacity: value = " + (float) animation.getAnimatedValue()); 227 setAlpha((float) animation.getAnimatedValue()); 228 }); 229 230 animator.addListener(new Animator.AnimatorListener() { 231 @Override 232 public void onAnimationStart(@NonNull Animator animation) { 233 234 } 235 236 @Override 237 public void onAnimationEnd(@NonNull Animator animation) { 238 Logger.d(TAG, "ScreenFlash#apply: onAnimationEnd"); 239 if (onAnimationEnd != null) { 240 onAnimationEnd.run(); 241 } 242 } 243 244 @Override 245 public void onAnimationCancel(@NonNull Animator animation) { 246 247 } 248 249 @Override 250 public void onAnimationRepeat(@NonNull Animator animation) { 251 252 } 253 }); 254 255 animator.start(); 256 257 return animator; 258 } 259 getBrightness()260 private float getBrightness() { 261 if (mScreenFlashWindow == null) { 262 Logger.e(TAG, "setBrightness: mScreenFlashWindow is null!"); 263 return Float.NaN; 264 } 265 266 WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes(); 267 return layoutParam.screenBrightness; 268 } 269 setBrightness(float value)270 private void setBrightness(float value) { 271 if (mScreenFlashWindow == null) { 272 Logger.e(TAG, "setBrightness: mScreenFlashWindow is null!"); 273 return; 274 } 275 276 if (Float.isNaN(value)) { 277 Logger.e(TAG, "setBrightness: value is NaN!"); 278 return; 279 } 280 281 WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes(); 282 layoutParam.screenBrightness = value; 283 mScreenFlashWindow.setAttributes(layoutParam); 284 Logger.d(TAG, "Brightness set to " + layoutParam.screenBrightness); 285 } 286 287 /** 288 * Returns an {@link ScreenFlash} implementation based on the {@link Window} instance 289 * set via {@link #setScreenFlashWindow(Window)}. 290 * 291 * <p> When {@link ScreenFlash#apply(long, ImageCapture.ScreenFlashListener)} is invoked, 292 * this view becomes fully visible gradually with an animation and screen brightness is 293 * maximized using the provided {@code Window}. Since brightness change of the display happens 294 * asynchronously and may take some time to be completed, the animation to ramp up visibility 295 * may require a duration of sufficient delay (decided internally) before 296 * {@link ImageCapture.ScreenFlashListener#onCompleted()} is invoked. 297 * 298 * <p> The default color of the overlay view is {@link Color#WHITE}. To change 299 * the color, use {@link #setBackgroundColor(int)}. 300 * 301 * <p> When {@link ScreenFlash#clear()} is invoked, the view 302 * becomes transparent and screen brightness is restored. 303 * 304 * <p> The {@code Window} instance parameter can usually be provided from the activity using 305 * the {@link PreviewView}, see {@link Activity#getWindow()} for details. If a null {@code 306 * Window} is set or none set at all, a null value will be returned by this method. 307 * 308 * @return A simple {@link ScreenFlash} implementation, or null value if a non-null 309 * {@code Window} instance hasn't been set. 310 */ 311 @UiThread getScreenFlash()312 public @Nullable ScreenFlash getScreenFlash() { 313 return mScreenFlash; 314 } 315 316 /** 317 * Returns the duration of the visibility ramp-up animation. 318 * 319 * <p> This is currently used in {@link ScreenFlash#apply}. 320 * 321 * @see #getScreenFlash() 322 */ 323 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getVisibilityRampUpAnimationDurationMillis()324 public long getVisibilityRampUpAnimationDurationMillis() { 325 return ANIMATION_DURATION_MILLIS; 326 } 327 } 328