| /* |
| * 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.ui; |
| |
| import android.os.Message; |
| import android.support.annotation.NonNull; |
| import androidx.leanback.widget.VerticalGridView; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import com.android.tv.common.WeakHandler; |
| |
| /** Listener to make focus change faster over time. */ |
| public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInterceptListener { |
| private static final String TAG = "OnRepeatedKeyListener"; |
| private static final boolean DEBUG = false; |
| |
| private static final int[] THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS = {2000, 5000}; |
| private static final int[] MAX_SKIPPED_VIEW_COUNT = {1, 4}; |
| private static final int MSG_MOVE_FOCUS = 1000; |
| |
| private final VerticalGridView mView; |
| private final MyHandler mHandler = new MyHandler(this); |
| private int mDirection; |
| private boolean mFocusAccelerated; |
| private long mRepeatedKeyInterval; |
| |
| public OnRepeatedKeyInterceptListener(VerticalGridView view) { |
| mView = view; |
| } |
| |
| public boolean isFocusAccelerated() { |
| return mFocusAccelerated; |
| } |
| |
| @Override |
| public boolean onInterceptKeyEvent(KeyEvent event) { |
| mHandler.removeMessages(MSG_MOVE_FOCUS); |
| if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_UP |
| && event.getKeyCode() != KeyEvent.KEYCODE_DPAD_DOWN) { |
| return false; |
| } |
| |
| long duration = event.getEventTime() - event.getDownTime(); |
| if (duration < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[0] || event.isCanceled()) { |
| mFocusAccelerated = false; |
| return false; |
| } |
| mDirection = |
| event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP ? View.FOCUS_UP : View.FOCUS_DOWN; |
| int skippedViewCount = MAX_SKIPPED_VIEW_COUNT[0]; |
| for (int i = 1; i < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS.length; ++i) { |
| if (THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[i] < duration) { |
| skippedViewCount = MAX_SKIPPED_VIEW_COUNT[i]; |
| } else { |
| break; |
| } |
| } |
| if (event.getAction() == KeyEvent.ACTION_DOWN) { |
| mRepeatedKeyInterval = duration / event.getRepeatCount(); |
| mFocusAccelerated = true; |
| } else { |
| // HACK: we move focus skippedViewCount times more even after ACTION_UP. Without this |
| // hack, a focused view's position doesn't reach to the desired position |
| // in ProgramGrid. |
| mFocusAccelerated = false; |
| } |
| for (int i = 0; i < skippedViewCount; ++i) { |
| mHandler.sendEmptyMessageDelayed( |
| MSG_MOVE_FOCUS, mRepeatedKeyInterval * i / (skippedViewCount + 1)); |
| } |
| if (DEBUG) Log.d(TAG, "onInterceptKeyEvent: focused view " + mView.findFocus()); |
| return false; |
| } |
| |
| private static class MyHandler extends WeakHandler<OnRepeatedKeyInterceptListener> { |
| private MyHandler(OnRepeatedKeyInterceptListener listener) { |
| super(listener); |
| } |
| |
| @Override |
| public void handleMessage(Message msg, @NonNull OnRepeatedKeyInterceptListener listener) { |
| if (msg.what == MSG_MOVE_FOCUS) { |
| View focused = listener.mView.findFocus(); |
| if (DEBUG) Log.d(TAG, "MSG_MOVE_FOCUS: focused view " + focused); |
| if (focused != null) { |
| View v = focused.focusSearch(listener.mDirection); |
| if (v != null && v != focused) { |
| v.requestFocus(listener.mDirection); |
| } |
| } |
| } |
| } |
| } |
| } |