blob: 5edf817d35b74c11ca56e00bdaa5c6504cb9c377 [file] [log] [blame]
/*
* 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);
}
}