• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.guide;
18 
19 import android.graphics.Rect;
20 import android.support.annotation.NonNull;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.ViewParent;
24 
25 import java.util.ArrayList;
26 import java.util.concurrent.TimeUnit;
27 
28 class GuideUtils {
29     private static final int INVALID_INDEX = -1;
30     private static int sWidthPerHour = 0;
31 
32     /**
33      * Sets the width in pixels that corresponds to an hour in program guide.
34      * Assume that this is called from main thread only, so, no synchronization.
35      */
setWidthPerHour(int widthPerHour)36     static void setWidthPerHour(int widthPerHour) {
37         sWidthPerHour = widthPerHour;
38     }
39 
40     /**
41      * Gets the number of pixels in program guide table that corresponds to the given milliseconds.
42      */
convertMillisToPixel(long millis)43     static int convertMillisToPixel(long millis) {
44         return (int) (millis * sWidthPerHour / TimeUnit.HOURS.toMillis(1));
45     }
46 
47     /**
48      * Gets the number of pixels in program guide table that corresponds to the given range.
49      */
convertMillisToPixel(long startMillis, long endMillis)50     static int convertMillisToPixel(long startMillis, long endMillis) {
51         // Convert to pixels first to avoid accumulation of rounding errors.
52         return GuideUtils.convertMillisToPixel(endMillis)
53                 - GuideUtils.convertMillisToPixel(startMillis);
54     }
55 
56     /**
57      * Gets the time in millis that corresponds to the given pixels in the program guide.
58      */
convertPixelToMillis(int pixel)59     static long convertPixelToMillis(int pixel) {
60         return pixel * TimeUnit.HOURS.toMillis(1) / sWidthPerHour;
61     }
62 
63     /**
64      * Return the view should be focused in the given program row according to the focus range.
65 
66      * @param keepCurrentProgramFocused If {@code true}, focuses on the current program if possible,
67      *                                  else falls back the general logic.
68      */
findNextFocusedProgram(View programRow, int focusRangeLeft, int focusRangeRight, boolean keepCurrentProgramFocused)69     static View findNextFocusedProgram(View programRow, int focusRangeLeft,
70             int focusRangeRight, boolean keepCurrentProgramFocused) {
71         ArrayList<View> focusables = new ArrayList<>();
72         findFocusables(programRow, focusables);
73 
74         if (keepCurrentProgramFocused) {
75             // Select the current program if possible.
76             for (int i = 0; i < focusables.size(); ++i) {
77                 View focusable = focusables.get(i);
78                 if (focusable instanceof ProgramItemView
79                         && isCurrentProgram((ProgramItemView) focusable)) {
80                     return focusable;
81                 }
82             }
83         }
84 
85         // Find the largest focusable among fully overlapped focusables.
86         int maxFullyOverlappedWidth = Integer.MIN_VALUE;
87         int maxPartiallyOverlappedWidth = Integer.MIN_VALUE;
88         int nextFocusIndex = INVALID_INDEX;
89         for (int i = 0; i < focusables.size(); ++i) {
90             View focusable = focusables.get(i);
91             Rect focusableRect = new Rect();
92             focusable.getGlobalVisibleRect(focusableRect);
93             if (focusableRect.left <= focusRangeLeft && focusRangeRight <= focusableRect.right) {
94                 // the old focused range is fully inside the focusable, return directly.
95                 return focusable;
96             } else if (focusRangeLeft <= focusableRect.left
97                     && focusableRect.right <= focusRangeRight) {
98                 // the focusable is fully inside the old focused range, choose the widest one.
99                 int width = focusableRect.width();
100                 if (width > maxFullyOverlappedWidth) {
101                     nextFocusIndex = i;
102                     maxFullyOverlappedWidth = width;
103                 }
104             } else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) {
105                 int overlappedWidth = (focusRangeLeft <= focusableRect.left) ?
106                         focusRangeRight - focusableRect.left
107                         : focusableRect.right - focusRangeLeft;
108                 if (overlappedWidth > maxPartiallyOverlappedWidth) {
109                     nextFocusIndex = i;
110                     maxPartiallyOverlappedWidth = overlappedWidth;
111                 }
112             }
113         }
114         if (nextFocusIndex != INVALID_INDEX) {
115             return focusables.get(nextFocusIndex);
116         }
117         return null;
118     }
119 
120     /**
121      *  Returns {@code true} if the program displayed in the give
122      *  {@link com.android.tv.guide.ProgramItemView} is a current program.
123      */
isCurrentProgram(ProgramItemView view)124     static boolean isCurrentProgram(ProgramItemView view) {
125         return view.getTableEntry().isCurrentProgram();
126     }
127 
128     /**
129      * Returns {@code true} if the given view is a descendant of the give container.
130      */
isDescendant(ViewGroup container, View view)131     static boolean isDescendant(ViewGroup container, View view) {
132         if (view == null) {
133             return false;
134         }
135         for (ViewParent p = view.getParent(); p != null; p = p.getParent()) {
136             if (p == container) {
137                 return true;
138             }
139         }
140         return false;
141     }
142 
findFocusables(View v, ArrayList<View> outFocusable)143     private static void findFocusables(View v, ArrayList<View> outFocusable) {
144         if (v.isFocusable()) {
145             outFocusable.add(v);
146         }
147         if (v instanceof ViewGroup) {
148             ViewGroup viewGroup = (ViewGroup) v;
149             for (int i = 0; i < viewGroup.getChildCount(); ++i) {
150                 findFocusables(viewGroup.getChildAt(i), outFocusable);
151             }
152         }
153     }
154 
GuideUtils()155     private GuideUtils() { }
156 }
157