aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/guide/GuideUtils.java
blob: 403d00b52c0a39a0793d023e53fcdb88b0b8d767 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * 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<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() { }
}