1 /* 2 * Copyright (C) 2017 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.incallui; 18 19 import android.support.annotation.NonNull; 20 import android.text.Editable; 21 import android.text.Spannable; 22 import android.text.SpannableString; 23 import android.text.method.DialerKeyListener; 24 import android.view.KeyEvent; 25 import android.view.View; 26 import com.android.dialer.common.LogUtil; 27 28 /** 29 * Key listener specialized to deal with Dtmf codes. 30 * 31 * <p>This listener will listen for valid Dtmf characters, and in response will inform the 32 * associated presenter of the character. As an implementation of {@link DialerKeyListener}, this 33 * class will listen for <b>hardware keyboard</b> events. 34 * 35 * <p>From legacy documentation: 36 * 37 * <ul> 38 * <li>Ignores the backspace since it is irrelevant. 39 * <li>Allow ONLY valid DTMF characters to generate a tone and be sent as a DTMF code. 40 * <li>All other remaining characters are handled by the superclass. 41 * <li>This code is purely here to handle events from the hardware keyboard while the DTMF dialpad 42 * is up. 43 * </ul> 44 */ 45 final class DtmfKeyListener extends DialerKeyListener { 46 /** 47 * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} These are the valid dtmf 48 * characters. 49 */ 50 private static final char[] VALID_DTMF_CHARACTERS = 51 new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'}; 52 53 /** 54 * Spannable used to call {@link DialerKeyListener#lookup(KeyEvent, Spannable)}, so it's not 55 * necessary to copy the implementation. 56 * 57 * <p>The Spannable is only used to determine which meta keys are pressed, e.g. shift, alt, see 58 * {@link android.text.method.MetaKeyKeyListener#getMetaState(CharSequence)}, so using a dummy 59 * value is fine here. 60 */ 61 private static final Spannable EMPTY_SPANNABLE = new SpannableString(""); 62 63 private final DialpadPresenter presenter; 64 65 DtmfKeyListener(@NonNull DialpadPresenter presenter) { 66 this.presenter = presenter; 67 } 68 69 @Override 70 protected char[] getAcceptedChars() { 71 return VALID_DTMF_CHARACTERS; 72 } 73 74 @Override 75 public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) { 76 return false; 77 } 78 79 /** 80 * Responds to keyDown events by firing a Dtmf tone, if the given event corresponds is a {@link 81 * #VALID_DTMF_CHARACTERS}. 82 * 83 * @return {@code true} if the event was handled. 84 */ 85 @Override 86 public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { 87 LogUtil.i("DtmfKeyListener.onKeyDown", "overload"); 88 if (!super.onKeyDown(view, content, keyCode, event)) { 89 LogUtil.i("DtmfKeyListener.onKeyDown", "parent type didn't support event"); 90 return false; 91 } 92 93 return onKeyDown(event); 94 } 95 96 /** 97 * Version of {@link #onKeyDown(View, Editable, int, KeyEvent)} used when a View/Editable isn't 98 * available. 99 */ 100 boolean onKeyDown(KeyEvent event) { 101 LogUtil.enterBlock("DtmfKeyListener.onKeyDown"); 102 if (event.getRepeatCount() != 0) { 103 LogUtil.i("DtmfKeyListener.onKeyDown", "long press, ignoring"); 104 return false; 105 } 106 107 char c = (char) lookup(event, EMPTY_SPANNABLE); 108 109 if (!ok(getAcceptedChars(), c)) { 110 LogUtil.i("DtmfKeyListener.onKeyDown", "not an accepted character"); 111 return false; 112 } 113 114 presenter.processDtmf(c); 115 return true; 116 } 117 118 /** 119 * Responds to keyUp events by stopping any playing Dtmf tone if the given event corresponds is a 120 * {@link #VALID_DTMF_CHARACTERS}. 121 * 122 * <p>Null events also stop the Dtmf tone. 123 * 124 * @return {@code true} if the event was handled 125 */ 126 @Override 127 public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { 128 LogUtil.i("DtmfKeyListener.onKeyUp", "overload"); 129 super.onKeyUp(view, content, keyCode, event); 130 131 return onKeyUp(event); 132 } 133 134 /** 135 * Handle individual keyup events. 136 * 137 * @param event is the event we are trying to stop. If this is null, then we just force-stop the 138 * last tone without checking if the event is an acceptable dialer event. 139 */ 140 boolean onKeyUp(KeyEvent event) { 141 LogUtil.enterBlock("DtmfKeyListener.onKeyUp"); 142 if (event == null) { 143 return true; 144 } 145 146 char c = (char) lookup(event, EMPTY_SPANNABLE); 147 148 if (!ok(getAcceptedChars(), c)) { 149 LogUtil.i("DtmfKeyListener.onKeyUp", "not an accepted character"); 150 return false; 151 } 152 153 presenter.stopDtmf(); 154 return true; 155 } 156 } 157