1 /* 2 * Copyright (C) 2023 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.systemui.qs 18 19 import android.view.KeyEvent 20 import android.view.View 21 import androidx.core.util.Consumer 22 23 /** 24 * Listens for left and right arrow keys pressed while focus is on the view. 25 * 26 * Key press is treated as correct when its full lifecycle happened on the view: first 27 * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then 28 * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode] 29 */ 30 class LeftRightArrowPressedListener private constructor() : 31 View.OnKeyListener, View.OnFocusChangeListener { 32 33 private var lastKeyCode: Int? = 0 34 private var listener: Consumer<Int>? = null 35 setArrowKeyPressedListenernull36 fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) { 37 listener = arrowPressedListener 38 } 39 onKeynull40 override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean { 41 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 42 // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need 43 // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we 44 // have a chance to intercept ACTION_UP. 45 if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) { 46 listener?.accept(keyCode) 47 lastKeyCode = null 48 } else if (keyEvent.repeatCount == 0) { 49 // we only read key events that are NOT coming from long pressing because that also 50 // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with 51 // arrow from another sibling view 52 lastKeyCode = keyCode 53 } 54 return true 55 } 56 return false 57 } 58 onFocusChangenull59 override fun onFocusChange(view: View, hasFocus: Boolean) { 60 // resetting lastKeyCode so we get fresh cleared state on focus 61 if (hasFocus) { 62 lastKeyCode = null 63 } 64 } 65 66 companion object { 67 @JvmStatic createAndRegisterListenerForViewnull68 fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener { 69 val listener = LeftRightArrowPressedListener() 70 view.setOnKeyListener(listener) 71 view.onFocusChangeListener = listener 72 return listener 73 } 74 } 75 } 76