Android Q - 通话过程中按数字键打开拨号盘发送dtmf

文章讲述了在AndroidInCallActivity中如何处理通话中的数字键输入,包括在按键按下时显示拨号盘,以及通过模拟点击事件来响应数字键。重点涉及DialpadFragment和DialpadKeyButton类,以及如何在可访问性模式下使用模拟点击功能以保持语义一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

查看源码发现默认在通话中,按数字键是没有反应的,也就是说我们要实现这个需求,首先得按数字键后显示拨号盘,接着实现点击效果。

修改路径:

packages/apps/Dialer/java/com/android/incallui/InCallActivity.java(通话中的页面)

packages/apps/Dialer/java/com/android/incallui/DialpadFragment.java(通话中的拨号盘)

packages/apps/Dialer/java/com/android/dialer/dialpadview/DialpadKeyButton.java(拨号盘的数字 button )

由于直接在当前页面按数字按键,实现按键监听即可,

InCallActivity.java

  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event) {
	/*lichang*/
	Log.d("lichang", "有按键按下" + keyCode);
	if (displayMap.containsKey(keyCode)) {
		DialpadFragment dialpadFragment = getDialpadFragment();
		if (event.getRepeatCount() == 0) {
			if (dialpadFragment == null) {
				showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
				dialpadFragment = getDialpadFragment();
				dialpadFragment.simulatedClick(displayMap.get(keyCode));
				return true;
			} else {
				if (!dialpadFragment.isVisible()) {
					showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
					dialpadFragment = getDialpadFragment();
				}
				dialpadFragment.simulatedClick(displayMap.get(keyCode));
				return false;
			}
		}
	}
    ..
  }

然后在实现点击效果

DialpadFragment.java

  public void simulatedClick(Character c) {
	Log.d("lichang", "simulatedClick 模拟点击 " + c);
	DialpadKeyButton dialpadKey;
    for (int i = 0; i < buttonIds.length; i++) {
	  if (displayMap.get(buttonIds[i]) == c) {
		Log.d("lichang", "simulatedClick 模拟 ");
		DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
		dialpadKey = (DialpadKeyButton) dialpadView.findViewById(buttonIds[i]);
		dialpadKey.simulateClickForAccessibility();
	  }
    }
  }

接下来简述一下点击事件思路,首先看到 DialpadKeyButton 以为就是个 button,结果调用 performClick() 方法后无效果,于是查看 DialpadKeyButton 

路径:

packages/apps/Dialer/java/com/android/dialer/dialpadview/DialpadKeyButton.java

/*
 * Copyright (C) 2012 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.dialer.dialpadview;

import android.content.Context;
import android.graphics.RectF;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;

/**
 * Custom class for dialpad buttons.
 *
 * <p>When touch exploration mode is enabled for accessibility, this class implements the
 * lift-to-type interaction model:
 *
 * <ul>
 * <li>Hovering over the button will cause it to gain accessibility focus
 * <li>Removing the hover pointer while inside the bounds of the button will perform a click action
 * <li>If long-click is supported, hovering over the button for a longer period of time will switch
 *     to the long-click action
 * <li>Moving the hover pointer outside of the bounds of the button will restore to the normal click
 *     action
 *     <ul>
 */
public class DialpadKeyButton extends FrameLayout {

  /** Accessibility manager instance used to check touch exploration state. */
  private AccessibilityManager accessibilityManager;

  /** Bounds used to filter HOVER_EXIT events. */
  private RectF hoverBounds = new RectF();

  /** Alternate content description for long-hover state. */
  private CharSequence longHoverContentDesc;

  /** Backup of clickable property. Used for accessibility. */
  private boolean wasClickable;

  /** Backup of long-clickable property. Used for accessibility. */
  private boolean wasLongClickable;

  private OnPressedListener onPressedListener;

  public DialpadKeyButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    initForAccessibility(context);
  }

  public DialpadKeyButton(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initForAccessibility(context);
  }

  public void setOnPressedListener(OnPressedListener onPressedListener) {
    this.onPressedListener = onPressedListener;
  }

  private void initForAccessibility(Context context) {
    accessibilityManager =
        (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
  }

  public void setLongHoverContentDescription(CharSequence contentDescription) {
    longHoverContentDesc = contentDescription;
  }

  @Override
  public void setPressed(boolean pressed) {
    super.setPressed(pressed);
    if (onPressedListener != null) {
      onPressedListener.onPressed(this, pressed);
    }
  }

  @Override
  public void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    hoverBounds.left = getPaddingLeft();
    hoverBounds.right = w - getPaddingRight();
    hoverBounds.top = getPaddingTop();
    hoverBounds.bottom = h - getPaddingBottom();
  }

  @Override
  public boolean performAccessibilityAction(int action, Bundle arguments) {
    if (action == AccessibilityNodeInfo.ACTION_CLICK) {
      simulateClickForAccessibility();
      return true;
    }

    return super.performAccessibilityAction(action, arguments);
  }

  @Override
  public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    // If the button has a long hover description, ask talkback to announce the action follow by
    // the description (for example "double tap and hold to call voicemail").
    if (!TextUtils.isEmpty(longHoverContentDesc)) {
      AccessibilityAction longClickAction =
          new AccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK, longHoverContentDesc);
      info.addAction(longClickAction);
    }
  }

  @Override
  public boolean onHoverEvent(MotionEvent event) {
    // When touch exploration is turned on, lifting a finger while inside
    // the button's hover target bounds should perform a click action.
    if (accessibilityManager.isEnabled() && accessibilityManager.isTouchExplorationEnabled()) {
      switch (event.getActionMasked()) {
        case MotionEvent.ACTION_HOVER_ENTER:
          // Lift-to-type temporarily disables double-tap activation.
          wasClickable = isClickable();
          wasLongClickable = isLongClickable();
          setClickable(false);
          setLongClickable(false);
          break;
        case MotionEvent.ACTION_HOVER_EXIT:
          if (hoverBounds.contains(event.getX(), event.getY())) {
            simulateClickForAccessibility();
          }

          setClickable(wasClickable);
          setLongClickable(wasLongClickable);
          break;
        default: // No-op
          break;
      }
    }

    return super.onHoverEvent(event);
  }

  /**
   * When accessibility is on, simulate press and release to preserve the semantic meaning of
   * performClick(). Required for Braille support.
   */
  public void simulateClickForAccessibility() {
    // Checking the press state prevents double activation.
    if (isPressed()) {
      return;
    }

    setPressed(true);

    // Stay consistent with performClick() by sending the event after
    // setting the pressed state but before performing the action.
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    setPressed(false);
  }

  public interface OnPressedListener {

    void onPressed(View view, boolean pressed);
  }
}

其中提到了当可访问性打开时,模拟按下和释放,以保留performClick()的语义含义。需要盲文支持。

完整patch如下:

diff --git a/java/com/android/dialer/dialpadview/DialpadKeyButton.java b/java/com/android/dialer/dialpadview/DialpadKeyButton.java
index 47553b6..d2c8e6d 100644
--- a/java/com/android/dialer/dialpadview/DialpadKeyButton.java
+++ b/java/com/android/dialer/dialpadview/DialpadKeyButton.java
@@ -159,7 +159,7 @@ public class DialpadKeyButton extends FrameLayout {
    * When accessibility is on, simulate press and release to preserve the semantic meaning of
    * performClick(). Required for Braille support.
    */
-  private void simulateClickForAccessibility() {
+  public void simulateClickForAccessibility() {
     // Checking the press state prevents double activation.
     if (isPressed()) {
       return;
diff --git a/java/com/android/incallui/DialpadFragment.java b/java/com/android/incallui/DialpadFragment.java
index f069818..e80c2ed 100644
--- a/java/com/android/incallui/DialpadFragment.java
+++ b/java/com/android/incallui/DialpadFragment.java
@@ -191,6 +191,19 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadUi>
     currentTextColor = textColor;
   }
 
+  public void simulatedClick(Character c) {
+	Log.d("lichang", "simulatedClick 模拟点击 " + c);
+	DialpadKeyButton dialpadKey;
+    for (int i = 0; i < buttonIds.length; i++) {
+	  if (displayMap.get(buttonIds[i]) == c) {
+		Log.d("lichang", "simulatedClick 模拟 ");
+		DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
+		dialpadKey = (DialpadKeyButton) dialpadView.findViewById(buttonIds[i]);
+		dialpadKey.simulateClickForAccessibility();
+	  }
+    }
+  }
+
   @Override
   public void onDestroyView() {
     dtmfKeyListener = null;
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 4e36046..8ab2c12 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -53,6 +53,7 @@ import android.telecom.PhoneAccountHandle;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.view.KeyEvent;
 import android.view.MenuItem;
 import android.view.MotionEvent;
@@ -118,6 +119,7 @@ import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.Map;
 
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;  //write external-storage
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;   //eard external-storage
@@ -147,6 +149,44 @@ public class InCallActivity extends TransactionSafeFragmentActivity
   private static final int DIALPAD_REQUEST_SHOW = 2;
   private static final int DIALPAD_REQUEST_HIDE = 3;
 
+  /*lichang*/
+
+  /** Hash Map to map a view id to a character */
+  private static final Map<Integer, Character> displayMap = new ArrayMap<>();
+
+  /** Set up the static maps */
+  static {
+    // Map the buttons to the display characters
+    displayMap.put(KeyEvent.KEYCODE_1, '1');
+    displayMap.put(KeyEvent.KEYCODE_2, '2');
+    displayMap.put(KeyEvent.KEYCODE_3, '3');
+    displayMap.put(KeyEvent.KEYCODE_4, '4');
+    displayMap.put(KeyEvent.KEYCODE_5, '5');
+    displayMap.put(KeyEvent.KEYCODE_6, '6');
+    displayMap.put(KeyEvent.KEYCODE_7, '7');
+    displayMap.put(KeyEvent.KEYCODE_8, '8');
+    displayMap.put(KeyEvent.KEYCODE_9, '9');
+    displayMap.put(KeyEvent.KEYCODE_0, '0');
+    displayMap.put(KeyEvent.KEYCODE_POUND, '#');
+    displayMap.put(KeyEvent.KEYCODE_STAR, '*');
+  }
+
+  private final int[] buttonIds =
+    new int[] {
+	  R.id.zero,
+	  R.id.one,
+	  R.id.two,
+	  R.id.three,
+	  R.id.four,
+	  R.id.five,
+	  R.id.six,
+	  R.id.seven,
+	  R.id.eight,
+	  R.id.nine,
+	  R.id.star,
+	  R.id.pound
+  };
+
   // UNISOC: add rejectmessage action in the notification.
   private static final String SHOW_REJECT_MESSAGE_DIALOG = "InCallActivity.reject_message_dialog";
   private static Optional<Integer> audioRouteForTesting = Optional.empty();
@@ -822,6 +862,26 @@ public class InCallActivity extends TransactionSafeFragmentActivity
 
   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) {
+	/*lichang*/
+	Log.d("lichang", "有按键按下" + keyCode);
+	if (displayMap.containsKey(keyCode)) {
+		DialpadFragment dialpadFragment = getDialpadFragment();
+		if (event.getRepeatCount() == 0) {
+			if (dialpadFragment == null) {
+				showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
+				dialpadFragment = getDialpadFragment();
+				dialpadFragment.simulatedClick(displayMap.get(keyCode));
+				return true;
+			} else {
+				if (!dialpadFragment.isVisible()) {
+					showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
+					dialpadFragment = getDialpadFragment();
+				}
+				dialpadFragment.simulatedClick(displayMap.get(keyCode));
+				return false;
+			}
+		}
+	}
     switch (keyCode) {
       case KeyEvent.KEYCODE_CALL:
         if (!InCallPresenter.getInstance().handleCallKey()) {

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值