/* * 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.support.annotation.NonNull; 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 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 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() { } }