Home | History | Annotate | Download | only in guide
      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      */
     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      */
     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      */
     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      */
     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      */
     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      */
    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      */
    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 
    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 
    155     private GuideUtils() { }
    156 }
    157