1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/* 2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project 3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License. 6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at 7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software 11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and 14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License. 15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv.guide; 18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.graphics.Rect; 206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.support.annotation.NonNull; 216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.view.View; 226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.view.ViewGroup; 236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.view.ViewParent; 246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.ArrayList; 26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit; 27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoclass GuideUtils { 296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko private static final int INVALID_INDEX = -1; 30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static int sWidthPerHour = 0; 31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Sets the width in pixels that corresponds to an hour in program guide. 34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Assume that this is called from main thread only, so, no synchronization. 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static void setWidthPerHour(int widthPerHour) { 37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sWidthPerHour = widthPerHour; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Gets the number of pixels in program guide table that corresponds to the given milliseconds. 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static int convertMillisToPixel(long millis) { 44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return (int) (millis * sWidthPerHour / TimeUnit.HOURS.toMillis(1)); 45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Gets the number of pixels in program guide table that corresponds to the given range. 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static int convertMillisToPixel(long startMillis, long endMillis) { 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Convert to pixels first to avoid accumulation of rounding errors. 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return GuideUtils.convertMillisToPixel(endMillis) 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - GuideUtils.convertMillisToPixel(startMillis); 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Gets the time in millis that corresponds to the given pixels in the program guide. 58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static long convertPixelToMillis(int pixel) { 60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return pixel * TimeUnit.HOURS.toMillis(1) / sWidthPerHour; 61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko /** 646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * Return the view should be focused in the given program row according to the focus range. 656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * @param keepCurrentProgramFocused If {@code true}, focuses on the current program if possible, 676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * else falls back the general logic. 686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko */ 696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static View findNextFocusedProgram(View programRow, int focusRangeLeft, 706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko int focusRangeRight, boolean keepCurrentProgramFocused) { 716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko ArrayList<View> focusables = new ArrayList<>(); 726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko findFocusables(programRow, focusables); 736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (keepCurrentProgramFocused) { 756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko // Select the current program if possible. 766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko for (int i = 0; i < focusables.size(); ++i) { 776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko View focusable = focusables.get(i); 786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (focusable instanceof ProgramItemView 796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko && isCurrentProgram((ProgramItemView) focusable)) { 806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return focusable; 816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko // Find the largest focusable among fully overlapped focusables. 866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko int maxFullyOverlappedWidth = Integer.MIN_VALUE; 876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko int maxPartiallyOverlappedWidth = Integer.MIN_VALUE; 886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko int nextFocusIndex = INVALID_INDEX; 896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko for (int i = 0; i < focusables.size(); ++i) { 906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko View focusable = focusables.get(i); 916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko Rect focusableRect = new Rect(); 926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko focusable.getGlobalVisibleRect(focusableRect); 936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (focusableRect.left <= focusRangeLeft && focusRangeRight <= focusableRect.right) { 946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko // the old focused range is fully inside the focusable, return directly. 956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return focusable; 966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } else if (focusRangeLeft <= focusableRect.left 976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko && focusableRect.right <= focusRangeRight) { 986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko // the focusable is fully inside the old focused range, choose the widest one. 996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko int width = focusableRect.width(); 1006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (width > maxFullyOverlappedWidth) { 1016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko nextFocusIndex = i; 1026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko maxFullyOverlappedWidth = width; 1036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) { 1056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko int overlappedWidth = (focusRangeLeft <= focusableRect.left) ? 1066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko focusRangeRight - focusableRect.left 1076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko : focusableRect.right - focusRangeLeft; 1086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (overlappedWidth > maxPartiallyOverlappedWidth) { 1096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko nextFocusIndex = i; 1106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko maxPartiallyOverlappedWidth = overlappedWidth; 1116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (nextFocusIndex != INVALID_INDEX) { 1156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return focusables.get(nextFocusIndex); 1166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return null; 1186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 1206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko /** 1216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * Returns {@code true} if the program displayed in the give 1226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * {@link com.android.tv.guide.ProgramItemView} is a current program. 1236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko */ 1246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static boolean isCurrentProgram(ProgramItemView view) { 1256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return view.getTableEntry().isCurrentProgram(); 1266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 1286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko /** 1296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * Returns {@code true} if the given view is a descendant of the give container. 1306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko */ 1316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko static boolean isDescendant(ViewGroup container, View view) { 1326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (view == null) { 1336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return false; 1346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko for (ViewParent p = view.getParent(); p != null; p = p.getParent()) { 1366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (p == container) { 1376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return true; 1386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return false; 1416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 1436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko private static void findFocusables(View v, ArrayList<View> outFocusable) { 1446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (v.isFocusable()) { 1456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko outFocusable.add(v); 1466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko if (v instanceof ViewGroup) { 1486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko ViewGroup viewGroup = (ViewGroup) v; 1496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko for (int i = 0; i < viewGroup.getChildCount(); ++i) { 1506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko findFocusables(viewGroup.getChildAt(i), outFocusable); 1516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 1546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private GuideUtils() { } 156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 157