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 package com.android.tv.ui;
17 
18 import android.os.Message;
19 import android.support.annotation.NonNull;
20 import android.support.v17.leanback.widget.VerticalGridView;
21 import android.util.Log;
22 import android.view.KeyEvent;
23 import android.view.View;
24 
25 import com.android.tv.common.WeakHandler;
26 
27 /**
28  * Listener to make focus change faster over time.
29  */
30 public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInterceptListener {
31     private static final String TAG = "OnRepeatedKeyListener";
32     private static final boolean DEBUG = false;
33 
34     private static final int[] THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS = { 2000, 5000 };
35     private static final int[] MAX_SKIPPED_VIEW_COUNT = { 1, 4 };
36     private static final int MSG_MOVE_FOCUS = 1000;
37 
38     private final VerticalGridView mView;
39     private final MyHandler mHandler = new MyHandler(this);
40     private int mDirection;
41     private boolean mFocusAccelerated;
42     private long mRepeatedKeyInterval;
43 
OnRepeatedKeyInterceptListener(VerticalGridView view)44     public OnRepeatedKeyInterceptListener(VerticalGridView view) {
45         mView = view;
46     }
47 
isFocusAccelerated()48     public boolean isFocusAccelerated() {
49         return mFocusAccelerated;
50     }
51 
52     @Override
onInterceptKeyEvent(KeyEvent event)53     public boolean onInterceptKeyEvent(KeyEvent event) {
54         mHandler.removeMessages(MSG_MOVE_FOCUS);
55         if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_UP &&
56                 event.getKeyCode() != KeyEvent.KEYCODE_DPAD_DOWN) {
57             return false;
58         }
59 
60         long duration = event.getEventTime() - event.getDownTime();
61         if (duration < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[0]
62                 || event.isCanceled()) {
63             mFocusAccelerated = false;
64             return false;
65         }
66         mDirection = event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP ? View.FOCUS_UP
67                 : View.FOCUS_DOWN;
68         int skippedViewCount = MAX_SKIPPED_VIEW_COUNT[0];
69         for (int i = 1 ;i < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS.length; ++i) {
70             if (THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[i] < duration) {
71                 skippedViewCount = MAX_SKIPPED_VIEW_COUNT[i];
72             } else {
73                 break;
74             }
75         }
76         if (event.getAction() == KeyEvent.ACTION_DOWN) {
77             mRepeatedKeyInterval = duration / event.getRepeatCount();
78             mFocusAccelerated = true;
79         } else {
80             // HACK: we move focus skippedViewCount times more even after ACTION_UP. Without this
81             // hack, a focused view's position doesn't reach to the desired position
82             // in ProgramGrid.
83             mFocusAccelerated = false;
84         }
85         for (int i = 0; i < skippedViewCount; ++i) {
86             mHandler.sendEmptyMessageDelayed(MSG_MOVE_FOCUS,
87                     mRepeatedKeyInterval * i / (skippedViewCount + 1));
88         }
89         if (DEBUG) Log.d(TAG, "onInterceptKeyEvent: focused view " + mView.findFocus());
90         return false;
91     }
92 
93     private static class MyHandler extends WeakHandler<OnRepeatedKeyInterceptListener> {
MyHandler(OnRepeatedKeyInterceptListener listener)94         private MyHandler(OnRepeatedKeyInterceptListener listener) {
95             super(listener);
96         }
97 
98         @Override
handleMessage(Message msg, @NonNull OnRepeatedKeyInterceptListener listener)99         public void handleMessage(Message msg, @NonNull OnRepeatedKeyInterceptListener listener) {
100             if (msg.what == MSG_MOVE_FOCUS) {
101                 View focused = listener.mView.findFocus();
102                 if (DEBUG) Log.d(TAG, "MSG_MOVE_FOCUS: focused view " + focused);
103                 if (focused != null) {
104                     View v = focused.focusSearch(listener.mDirection);
105                     if (v != null && v != focused) {
106                         v.requestFocus(listener.mDirection);
107                     }
108                 }
109             }
110         }
111     }
112 }
113