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