| /* |
| * Copyright (C) 2014 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 com.android.camera; |
| |
| import android.content.Context; |
| import android.graphics.Matrix; |
| import android.graphics.RectF; |
| |
| import com.android.camera.app.CameraApp; |
| import com.android.camera.app.CameraAppUI; |
| import com.android.camera.ui.PreviewStatusListener; |
| import com.android.camera2.R; |
| |
| /** |
| * This class centralizes the logic of how bottom bar should be laid out and how |
| * preview should be transformed. The two things that could affect bottom bar layout |
| * and preview transform are: window size and preview aspect ratio. Once these two |
| * things are set, the layout of bottom bar and preview rect will be calculated |
| * and can then be queried anywhere inside the app. |
| * |
| * Note that this helper assumes that preview TextureView will be laid out full |
| * screen, meaning all its ascendants are laid out with MATCH_PARENT flags. If |
| * or when this assumption is no longer the case, we need to revisit this logic. |
| */ |
| public class CaptureLayoutHelper implements CameraAppUI.NonDecorWindowSizeChangedListener, |
| PreviewStatusListener.PreviewAspectRatioChangedListener { |
| |
| private final int mBottomBarMinHeight; |
| private final int mBottomBarMaxHeight; |
| private final int mBottomBarOptimalHeight; |
| |
| private int mWindowWidth = 0; |
| private int mWindowHeight = 0; |
| /** Aspect ratio of preview. It could be 0, meaning match the screen aspect ratio, |
| * or a float value no less than 1f. |
| */ |
| private float mAspectRatio = TextureViewHelper.MATCH_SCREEN; |
| private PositionConfiguration mPositionConfiguration = null; |
| private int mRotation = 0; |
| private boolean mShowBottomBar = true; |
| |
| /** |
| * PositionConfiguration contains the layout info for bottom bar and preview |
| * rect, as well as whether bottom bar should be overlaid on top of preview. |
| */ |
| public static final class PositionConfiguration { |
| /** |
| * This specifies the rect of preview on screen. |
| */ |
| public final RectF mPreviewRect = new RectF(); |
| /** |
| * This specifies the rect where bottom bar should be laid out in. |
| */ |
| public final RectF mBottomBarRect = new RectF(); |
| /** |
| * This indicates whether bottom bar should overlay itself on top of preview. |
| */ |
| public boolean mBottomBarOverlay = false; |
| } |
| |
| public CaptureLayoutHelper(int bottomBarMinHeight, int bottomBarMaxHeight, |
| int bottomBarOptimalHeight) { |
| mBottomBarMinHeight = bottomBarMinHeight; |
| mBottomBarMaxHeight = bottomBarMaxHeight; |
| mBottomBarOptimalHeight = bottomBarOptimalHeight; |
| } |
| |
| @Override |
| public void onPreviewAspectRatioChanged(float aspectRatio) { |
| if (mAspectRatio == aspectRatio) { |
| return; |
| } |
| mAspectRatio = aspectRatio; |
| updatePositionConfiguration(); |
| } |
| |
| /** |
| * Sets whether bottom bar will show or not. This will affect the calculation |
| * of uncovered preview area, which is used to lay out mode list, mode options, |
| * etc. |
| */ |
| public void setShowBottomBar(boolean showBottomBar) { |
| mShowBottomBar = showBottomBar; |
| } |
| |
| /** |
| * Updates bottom bar rect and preview rect. This gets called whenever |
| * preview aspect ratio changes or main activity layout size changes. |
| */ |
| private void updatePositionConfiguration() { |
| if (mWindowWidth == 0 || mWindowHeight == 0) { |
| return; |
| } |
| mPositionConfiguration = getPositionConfiguration(mWindowWidth, mWindowHeight, mAspectRatio, |
| mRotation); |
| } |
| |
| /** |
| * Returns the rect that bottom bar should be laid out in. If not enough info |
| * has been provided to calculate this, return an empty rect. Note that the rect |
| * returned is relative to the content layout of the activity. It may need to be |
| * translated based on the parent view's location. |
| */ |
| public RectF getBottomBarRect() { |
| if (mPositionConfiguration == null) { |
| updatePositionConfiguration(); |
| } |
| // Not enough info to create a position configuration. |
| if (mPositionConfiguration == null) { |
| return new RectF(); |
| } |
| return new RectF(mPositionConfiguration.mBottomBarRect); |
| } |
| |
| /** |
| * Returns the rect that preview should occupy based on aspect ratio. If not |
| * enough info has been provided to calculate this, return an empty rect. Note |
| * that the rect returned is relative to the content layout of the activity. |
| * It may need to be translated based on the parent view's location. |
| */ |
| public RectF getPreviewRect() { |
| if (mPositionConfiguration == null) { |
| updatePositionConfiguration(); |
| } |
| // Not enough info to create a position configuration. |
| if (mPositionConfiguration == null) { |
| return new RectF(); |
| } |
| return new RectF(mPositionConfiguration.mPreviewRect); |
| } |
| |
| /** |
| * This returns the rect that is available to display the preview, and |
| * capture buttons |
| * |
| * @return the rect. |
| */ |
| public RectF getFullscreenRect() { |
| return new RectF(0, 0, mWindowWidth, mWindowHeight); |
| } |
| |
| /** |
| * Returns the sub-rect of the preview that is not being blocked by the |
| * bottom bar. This can be used to lay out mode options, settings button, |
| * etc. If not enough info has been provided to calculate this, return an |
| * empty rect. Note that the rect returned is relative to the content layout |
| * of the activity. It may need to be translated based on the parent view's |
| * location. |
| */ |
| public RectF getUncoveredPreviewRect() { |
| if (mPositionConfiguration == null) { |
| updatePositionConfiguration(); |
| } |
| // Not enough info to create a position configuration. |
| if (mPositionConfiguration == null) { |
| return new RectF(); |
| } |
| |
| if (!RectF.intersects(mPositionConfiguration.mBottomBarRect, |
| mPositionConfiguration.mPreviewRect) || !mShowBottomBar) { |
| return mPositionConfiguration.mPreviewRect; |
| } |
| |
| if (mWindowHeight > mWindowWidth) { |
| // Portrait. |
| if (mRotation >= 180) { |
| // Reverse portrait, bottom bar align top. |
| return new RectF(mPositionConfiguration.mPreviewRect.left, |
| mPositionConfiguration.mBottomBarRect.bottom, |
| mPositionConfiguration.mPreviewRect.right, |
| mPositionConfiguration.mPreviewRect.bottom); |
| } else { |
| return new RectF(mPositionConfiguration.mPreviewRect.left, |
| mPositionConfiguration.mPreviewRect.top, |
| mPositionConfiguration.mPreviewRect.right, |
| mPositionConfiguration.mBottomBarRect.top); |
| } |
| } else { |
| if (mRotation >= 180) { |
| // Reverse landscape, bottom bar align left. |
| return new RectF(mPositionConfiguration.mBottomBarRect.right, |
| mPositionConfiguration.mPreviewRect.top, |
| mPositionConfiguration.mPreviewRect.right, |
| mPositionConfiguration.mPreviewRect.bottom); |
| } else { |
| return new RectF(mPositionConfiguration.mPreviewRect.left, |
| mPositionConfiguration.mPreviewRect.top, |
| mPositionConfiguration.mBottomBarRect.left, |
| mPositionConfiguration.mPreviewRect.bottom); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether the bottom bar should be transparent and overlaid on top |
| * of the preview. |
| */ |
| public boolean shouldOverlayBottomBar() { |
| if (mPositionConfiguration == null) { |
| updatePositionConfiguration(); |
| } |
| // Not enough info to create a position configuration. |
| if (mPositionConfiguration == null) { |
| return false; |
| } |
| return mPositionConfiguration.mBottomBarOverlay; |
| } |
| |
| @Override |
| public void onNonDecorWindowSizeChanged(int width, int height, int rotation) { |
| mWindowWidth = width; |
| mWindowHeight = height; |
| mRotation = rotation; |
| updatePositionConfiguration(); |
| } |
| |
| /** |
| * Calculates the layout rect of bottom bar and the size of preview based on |
| * activity layout width, height and aspect ratio. |
| * |
| * @param width width of the main activity layout, excluding system decor such |
| * as status bar, nav bar, etc. |
| * @param height height of the main activity layout, excluding system decor |
| * such as status bar, nav bar, etc. |
| * @param previewAspectRatio aspect ratio of the preview |
| * @param rotation rotation from the natural orientation |
| * @return a custom position configuration that contains bottom bar rect, |
| * preview rect and whether bottom bar should be overlaid. |
| */ |
| private PositionConfiguration getPositionConfiguration(int width, int height, |
| float previewAspectRatio, int rotation) { |
| boolean landscape = width > height; |
| |
| // If the aspect ratio is defined as fill the screen, then preview should |
| // take the screen rect. |
| PositionConfiguration config = new PositionConfiguration(); |
| if (previewAspectRatio == TextureViewHelper.MATCH_SCREEN) { |
| config.mPreviewRect.set(0, 0, width, height); |
| config.mBottomBarOverlay = true; |
| if (landscape) { |
| config.mBottomBarRect.set(width - mBottomBarOptimalHeight, 0, width, height); |
| } else { |
| config.mBottomBarRect.set(0, height - mBottomBarOptimalHeight, width, height); |
| } |
| } else { |
| if (previewAspectRatio < 1) { |
| previewAspectRatio = 1 / previewAspectRatio; |
| } |
| // Get the bottom bar width and height. |
| float barSize; |
| int longerEdge = Math.max(width, height); |
| int shorterEdge = Math.min(width, height); |
| |
| // Check the remaining space if fit short edge. |
| float spaceNeededAlongLongerEdge = shorterEdge * previewAspectRatio; |
| float remainingSpaceAlongLongerEdge = longerEdge - spaceNeededAlongLongerEdge; |
| |
| float previewShorterEdge; |
| float previewLongerEdge; |
| if (remainingSpaceAlongLongerEdge <= 0) { |
| // Preview aspect ratio > screen aspect ratio: fit longer edge. |
| previewLongerEdge = longerEdge; |
| previewShorterEdge = longerEdge / previewAspectRatio; |
| barSize = mBottomBarOptimalHeight; |
| config.mBottomBarOverlay = true; |
| |
| if (landscape) { |
| config.mPreviewRect.set(0, height / 2 - previewShorterEdge / 2, previewLongerEdge, |
| height / 2 + previewShorterEdge / 2); |
| config.mBottomBarRect.set(width - barSize, height / 2 - previewShorterEdge / 2, |
| width, height / 2 + previewShorterEdge / 2); |
| } else { |
| config.mPreviewRect.set(width / 2 - previewShorterEdge / 2, 0, |
| width / 2 + previewShorterEdge / 2, previewLongerEdge); |
| config.mBottomBarRect.set(width / 2 - previewShorterEdge / 2, height - barSize, |
| width / 2 + previewShorterEdge / 2, height); |
| } |
| } else if (previewAspectRatio > 14f / 9f) { |
| // If the preview aspect ratio is large enough, simply offset the |
| // preview to the bottom/right. |
| // TODO: This logic needs some refinement. |
| barSize = mBottomBarOptimalHeight; |
| previewShorterEdge = shorterEdge; |
| previewLongerEdge = shorterEdge * previewAspectRatio; |
| config.mBottomBarOverlay = true; |
| if (landscape) { |
| float right = width; |
| float left = right - previewLongerEdge; |
| config.mPreviewRect.set(left, 0, right, previewShorterEdge); |
| config.mBottomBarRect.set(width - barSize, 0, width, height); |
| } else { |
| float bottom = height; |
| float top = bottom - previewLongerEdge; |
| config.mPreviewRect.set(0, top, previewShorterEdge, bottom); |
| config.mBottomBarRect.set(0, height - barSize, width, height); |
| } |
| } else if (remainingSpaceAlongLongerEdge <= mBottomBarMinHeight) { |
| // Need to scale down the preview to fit in the space excluding the bottom bar. |
| previewLongerEdge = longerEdge - mBottomBarMinHeight; |
| previewShorterEdge = previewLongerEdge / previewAspectRatio; |
| barSize = mBottomBarMinHeight; |
| config.mBottomBarOverlay = false; |
| if (landscape) { |
| config.mPreviewRect.set(0, height / 2 - previewShorterEdge / 2, previewLongerEdge, |
| height / 2 + previewShorterEdge / 2); |
| config.mBottomBarRect.set(width - barSize, height / 2 - previewShorterEdge / 2, |
| width, height / 2 + previewShorterEdge / 2); |
| } else { |
| config.mPreviewRect.set(width / 2 - previewShorterEdge / 2, 0, |
| width / 2 + previewShorterEdge / 2, previewLongerEdge); |
| config.mBottomBarRect.set(width / 2 - previewShorterEdge / 2, height - barSize, |
| width / 2 + previewShorterEdge / 2, height); |
| } |
| } else { |
| // Fit shorter edge. |
| barSize = remainingSpaceAlongLongerEdge <= mBottomBarMaxHeight ? |
| remainingSpaceAlongLongerEdge : mBottomBarMaxHeight; |
| previewShorterEdge = shorterEdge; |
| previewLongerEdge = shorterEdge * previewAspectRatio; |
| config.mBottomBarOverlay = false; |
| if (landscape) { |
| float right = width - barSize; |
| float left = right - previewLongerEdge; |
| config.mPreviewRect.set(left, 0, right, previewShorterEdge); |
| config.mBottomBarRect.set(width - barSize, 0, width, height); |
| } else { |
| float bottom = height - barSize; |
| float top = bottom - previewLongerEdge; |
| config.mPreviewRect.set(0, top, previewShorterEdge, bottom); |
| config.mBottomBarRect.set(0, height - barSize, width, height); |
| } |
| } |
| } |
| |
| if (rotation >= 180) { |
| // Rotate 180 degrees. |
| Matrix rotate = new Matrix(); |
| rotate.setRotate(180, width / 2, height / 2); |
| |
| rotate.mapRect(config.mPreviewRect); |
| rotate.mapRect(config.mBottomBarRect); |
| } |
| |
| // Round the rect first to avoid rounding errors later on. |
| round(config.mBottomBarRect); |
| round(config.mPreviewRect); |
| |
| return config; |
| } |
| |
| /** |
| * Round the float coordinates in the given rect, and store the rounded value |
| * back in the rect. |
| */ |
| public static void round(RectF rect) { |
| if (rect == null) { |
| return; |
| } |
| float left = Math.round(rect.left); |
| float top = Math.round(rect.top); |
| float right = Math.round(rect.right); |
| float bottom = Math.round(rect.bottom); |
| rect.set(left, top, right, bottom); |
| } |
| } |