blob: d34350362281fbc7835b1d5b585cd75b786a34f6 [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.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;
/**
* 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();
}
/**
* 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);
}
/**
* 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);
}
/**
* 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 (mWindowHeight > mWindowWidth) {
// Portrait.
return new RectF(mPositionConfiguration.mPreviewRect.left,
mPositionConfiguration.mPreviewRect.top,
mPositionConfiguration.mPreviewRect.right,
mPositionConfiguration.mBottomBarRect.top);
} 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) {
mWindowWidth = width;
mWindowHeight = height;
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
* @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) {
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);
}
return config;
}
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 (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);
}
}
return config;
}
}