blob: 51c14fd4b7ade2d0b68e576e5c59aa491f467301 [file] [log] [blame]
/*
* Copyright (C) 2015 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.tv.guide;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
class GuideUtils {
private static final int INVALID_INDEX = -1;
private static int sWidthPerHour = 0;
/**
* Sets the width in pixels that corresponds to an hour in program guide. Assume that this is
* called from main thread only, so, no synchronization.
*/
static void setWidthPerHour(int widthPerHour) {
sWidthPerHour = widthPerHour;
}
/**
* Gets the number of pixels in program guide table that corresponds to the given milliseconds.
*/
static int convertMillisToPixel(long millis) {
return (int) (millis * sWidthPerHour / TimeUnit.HOURS.toMillis(1));
}
/** Gets the number of pixels in program guide table that corresponds to the given range. */
static int convertMillisToPixel(long startMillis, long endMillis) {
// Convert to pixels first to avoid accumulation of rounding errors.
return GuideUtils.convertMillisToPixel(endMillis)
- GuideUtils.convertMillisToPixel(startMillis);
}
/** Gets the time in millis that corresponds to the given pixels in the program guide. */
static long convertPixelToMillis(int pixel) {
return pixel * TimeUnit.HOURS.toMillis(1) / sWidthPerHour;
}
/**
* Return the view should be focused in the given program row according to the focus range.
*
* @param keepCurrentProgramFocused If {@code true}, focuses on the current program if possible,
* else falls back the general logic.
*/
static View findNextFocusedProgram(
View programRow,
int focusRangeLeft,
int focusRangeRight,
boolean keepCurrentProgramFocused) {
ArrayList<View> focusables = new ArrayList<>();
findFocusables(programRow, focusables);
if (keepCurrentProgramFocused) {
// Select the current program if possible.
for (int i = 0; i < focusables.size(); ++i) {
View focusable = focusables.get(i);
if (focusable instanceof ProgramItemView
&& isCurrentProgram((ProgramItemView) focusable)) {
return focusable;
}
}
}
// Find the largest focusable among fully overlapped focusables.
int maxFullyOverlappedWidth = Integer.MIN_VALUE;
int maxPartiallyOverlappedWidth = Integer.MIN_VALUE;
int nextFocusIndex = INVALID_INDEX;
for (int i = 0; i < focusables.size(); ++i) {
View focusable = focusables.get(i);
Rect focusableRect = new Rect();
focusable.getGlobalVisibleRect(focusableRect);
if (focusableRect.left <= focusRangeLeft && focusRangeRight <= focusableRect.right) {
// the old focused range is fully inside the focusable, return directly.
return focusable;
} else if (focusRangeLeft <= focusableRect.left
&& focusableRect.right <= focusRangeRight) {
// the focusable is fully inside the old focused range, choose the widest one.
int width = focusableRect.width();
if (width > maxFullyOverlappedWidth) {
nextFocusIndex = i;
maxFullyOverlappedWidth = width;
}
} else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) {
int overlappedWidth =
(focusRangeLeft <= focusableRect.left)
? focusRangeRight - focusableRect.left
: focusableRect.right - focusRangeLeft;
if (overlappedWidth > maxPartiallyOverlappedWidth) {
nextFocusIndex = i;
maxPartiallyOverlappedWidth = overlappedWidth;
}
}
}
if (nextFocusIndex != INVALID_INDEX) {
return focusables.get(nextFocusIndex);
}
return null;
}
/**
* Returns {@code true} if the program displayed in the give {@link
* com.android.tv.guide.ProgramItemView} is a current program.
*/
static boolean isCurrentProgram(ProgramItemView view) {
return view.getTableEntry().isCurrentProgram();
}
/** Returns {@code true} if the given view is a descendant of the give container. */
static boolean isDescendant(ViewGroup container, View view) {
if (view == null) {
return false;
}
for (ViewParent p = view.getParent(); p != null; p = p.getParent()) {
if (p == container) {
return true;
}
}
return false;
}
private static void findFocusables(View v, ArrayList<View> outFocusable) {
if (v.isFocusable()) {
outFocusable.add(v);
}
if (v instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) v;
for (int i = 0; i < viewGroup.getChildCount(); ++i) {
findFocusables(viewGroup.getChildAt(i), outFocusable);
}
}
}
private GuideUtils() {}
}