当前位置: 首页 > ds >正文

【Audio】静音或振动模式下重复来电响铃

一、问题描述

基于 Android 14平台,在静音或振动模式下来电是不会播放铃声的,会存在漏掉来电的情况,需要考虑紧急情况下的重复来电,例如在 10 分钟内来电 4 次,在这种情况下需要响铃来提醒用户。

请添加图片描述

二、问题分析

packages/services/Telecomm 这里主要分析最接近响铃的流程,主要是在 Telecomm 应用中,最主要的是 Ringer 这个类

public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {boolean deferBlockOnRingingFuture = false;// try-finally to ensure that the block on ringing future is always called.try {if (foregroundCall == null) {Log.wtf(this, "startRinging called with null foreground call.");return false;}if (foregroundCall.getState() != CallState.RINGING&& foregroundCall.getState() != CallState.SIMULATED_RINGING) {// It's possible for bluetooth to connect JUST as a call goes active, which would// mean the call would start ringing again.Log.i(this, "startRinging called for non-ringing foreground callid=%s",foregroundCall.getId());return false;}// Use completable future to establish a timeout, not intent to make these work outside// the main thread asynchronously// TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blockingCompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture.supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),new LoggedHandlerExecutor(getHandler(), "R.sR", null));RingerAttributes attributes = null;try {mAttributesLatch = new CountDownLatch(1);attributes = ringerAttributesFuture.get(RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);} catch (ExecutionException | InterruptedException | TimeoutException e) {// Keep attributes as nullLog.i(this, "getAttributes error: " + e);}if (attributes == null) {Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,"RingerAttributes error");return false;}if (attributes.isEndEarly()) {boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();if (attributes.letDialerHandleRinging()) {Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");// Dialer will setup a ringtone, provide the audio focus if its audible.acquireAudioFocus |= attributes.isRingerAudible();}if (attributes.isSilentRingingRequested()) {Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "+ "requested");}if (attributes.isWorkProfileInQuietMode()) {Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,"Work profile in quiet mode");}return acquireAudioFocus;}......mUniRinger.updateCVRSRingingState(foregroundCall, attributes.shouldRingForContact());if (foregroundCall.isCVRSCall()) {// used mDefaultVibrationEffect} else if (attributes.isRingerAudible()) {mUniRinger.setVoiceCVRSVolume(true, 0);mUniRinger.muteCVRSFromSilenceRinger(false);mUniRinger.setCVRSPlaying(false);/* @}*/mRingingCall = foregroundCall;Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);// Because we wait until a contact info query to complete before processing a// call (for the purposes of direct-to-voicemail), the information about custom// ringtones should be available by the time this code executes. We can safely// request the custom ringtone from the call and expect it to be current.if (shouldApplyRampingRinger) {Log.i(this, "create ramping ringer.");float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)/ (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);mVolumeShaperConfig =new VolumeShaper.Configuration.Builder().setDuration(RAMPING_RINGER_VIBRATION_DURATION+ RAMPING_RINGER_DURATION).setCurve(new float[]{0.f, silencePoint + EPSILON/*keep monotonicity*/, 1.f},new float[]{0.f, 0.f, 1.f}).setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR).build();if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {useCustomVibrationEffect = true;}} else {if (DEBUG_RINGER) {Log.i(this, "Create ringer with custom vibration effect");}// Ramping ringtone is not enabled.useCustomVibrationEffect = true;}} else {Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,"Inaudible: " + attributes.getInaudibleReason()+ " isVibratorEnabled=" + isVibratorEnabled);if (isVibratorEnabled) {// If ringer is not audible for this call, then the phone is in "Vibrate" mode.// Use haptic-only ringtone or do not play anything.isHapticOnly = true;if (DEBUG_RINGER) {Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);}} else {foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);return attributes.shouldAcquireAudioFocus(); // ringer not audible}}......// 初始化播放器/* UNISOC: add for CVRS(customized video ringing signal) @{ */if (foregroundCall.isCVRSCall()) {ringtoneSupplier  = null;Log.i(this, "is CVRS call, skip ringtone play");/* @}*/}else if (isHapticOnly) {if (hapticChannelsMuted) {Log.i(this,"want haptic only ringtone but haptics are muted, skip ringtone play");ringtoneSupplier = null;} else {ringtoneSupplier = mRingtoneFactory::getHapticOnlyRingtone;}} else {ringtoneSupplier = () -> mRingtoneFactory.getRingtone(foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);}......// 实际播放位置if (ringtoneSupplier != null) {mRingtonePlayer.play(ringtoneSupplier, afterRingtoneLogic);} else {afterRingtoneLogic.accept(/* ringtone= */ null, /* stopped= */ false);}// shouldAcquireAudioFocus is meant to be true, but that check is deferred to here// because until now is when we actually know if the ringtone loading worked.return attributes.shouldAcquireAudioFocus()|| (!isHapticOnly && attributes.isRingerAudible());......
}

获取播放参数,核心变量是 isRingerAudible 用于是否播放铃声

private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {mAudioManager = mContext.getSystemService(AudioManager.class);RingerAttributes.Builder builder = new RingerAttributes.Builder();LogUtils.EventTimer timer = new EventTimer();boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;timer.record("isVolumeOverZero");boolean shouldRingForContact = shouldRingForContact(call);timer.record("shouldRingForContact");boolean isSelfManaged = call.isSelfManaged();timer.record("isSelfManaged");boolean isSilentRingingRequested = call.isSilentRingingRequested();timer.record("isSilentRingRequested");boolean isRingerAudible = isVolumeOverZero && shouldRingForContact;timer.record("isRingerAudible");String inaudibleReason = "";if (!isRingerAudible) {inaudibleReason = String.format("isVolumeOverZero=%s, shouldRingForContact=%s",isVolumeOverZero, shouldRingForContact);}boolean hasExternalRinger = hasExternalRinger(call);timer.record("hasExternalRinger");// Don't do call waiting operations or vibration unless these are false.boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);timer.record("isTheaterModeOn");boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(call.getAssociatedUser());timer.record("letDialerHandleRinging");boolean isWorkProfileInQuietMode =isProfileInQuietMode(call.getAssociatedUser());timer.record("isWorkProfileInQuietMode");Log.i(this, "startRinging timings: " + timer);boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;if (endEarly) {Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +"isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +"isWorkProfileInQuietMode=%s",isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,isSilentRingingRequested, isWorkProfileInQuietMode);}// Acquire audio focus under any of the following conditions:// 1. Should ring for contact and there's an HFP device attached// 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone//    present. (This check is deferred until ringer knows the ringtone)// 3. The call is self-managed.boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged) ||(call.isCVRSCall() && !endEarly && shouldRingForContact); // UNISOC: add for CVRS(customized video ringing signal)// Set missed reason according to attributesif (!isVolumeOverZero) {call.setUserMissed(USER_MISSED_LOW_RING_VOLUME);}if (!shouldRingForContact) {call.setUserMissed(USER_MISSED_DND_MODE);}mAttributesLatch.countDown();return builder.setEndEarly(endEarly).setLetDialerHandleRinging(letDialerHandleRinging).setAcquireAudioFocus(shouldAcquireAudioFocus).setRingerAudible(isRingerAudible).setInaudibleReason(inaudibleReason).setShouldRingForContact(shouldRingForContact).setSilentRingingRequested(isSilentRingingRequested).setWorkProfileQuietMode(isWorkProfileInQuietMode).build();
}

三、解决方案

修改 Ringer 是核心,通过 统计来电次数来计算是否达到响铃的阈值,是否响铃通过 isRingerAudible 变量控制,通过修改 isRingerAudible 达到响铃的条件,为了不影响未打开 Settings 值的逻辑,使用逻辑与 的短路效应将开关条件前置

packages/services/Telecomm/src/com/android/server/telecom/Ringer.java

diff --git a/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/Ringer.java b/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/Ringer.java
index 454ac41..b8511bc 100755
--- a/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/Ringer.java
+++ b/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/Ringer.java
@@ -38,6 +38,7 @@ import android.os.UserManager;import android.os.VibrationAttributes;import android.os.VibrationEffect;import android.os.Vibrator;
+import android.provider.Settings;import android.telecom.Log;import android.telecom.TelecomManager;import android.view.accessibility.AccessibilityManager;
@@ -49,12 +50,16 @@ import com.unisoc.server.telecom.UniTelecomComponentFactory;import java.util.ArrayList;import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;import java.util.concurrent.TimeoutException;import java.util.function.BiConsumer;import java.util.function.Supplier;
+import java.util.Iterator;
+import java.util.List;/*** Controls the ringtone player.
@@ -197,6 +202,12 @@ public class Ringer {*/private final Object mLock;+    //*/ support auto mute media and ring in silent.
+    private final ConcurrentHashMap<String, List<Long>> mRecentCallHistory = new ConcurrentHashMap<>();
+    private static final long REPEAT_CALL_WINDOW_MS = 10 * 60 * 1000;
+    private static final int REPEAT_CALL_THRESHOLD = 4;
+    //*/
+public UniRinger mUniRinger;/** Initializes the Ringer. */
@@ -295,7 +306,9 @@ public class Ringer {"RingerAttributes error");return false;}
-
+            //*/ support auto mute media and ring in silent.
+            final boolean forceRing = attributes.isForceRing();
+            //*/if (attributes.isEndEarly()) {boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();if (attributes.letDialerHandleRinging()) {
@@ -424,7 +437,7 @@ public class Ringer {}} else {ringtoneSupplier = () -> mRingtoneFactory.getRingtone(
-                        foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);
+                        foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted, forceRing);}// If vibration will be done, reserve the vibrator.
@@ -706,7 +719,15 @@ public class Ringer {RingerAttributes.Builder builder = new RingerAttributes.Builder();LogUtils.EventTimer timer = new EventTimer();
-
+        //*/  support auto mute media and ring in silent.
+        boolean forceRing = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.RING_IN_SILENT_ENABLED, 0) == 1
+                && mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL
+                && shouldForceRingForRepeatCaller(call);
+        if (forceRing) {
+            Log.i(this, "Force ring is enabled for this call due to repeat calling.");
+        }
+        //*/boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;timer.record("isVolumeOverZero");boolean shouldRingForContact = shouldRingForContact(call);
@@ -716,7 +737,11 @@ public class Ringer {boolean isSilentRingingRequested = call.isSilentRingingRequested();timer.record("isSilentRingRequested");+        /*/ support auto mute media and ring in silent.boolean isRingerAudible = isVolumeOverZero && shouldRingForContact;
+        /*/
+        boolean isRingerAudible = (isVolumeOverZero && shouldRingForContact) || forceRing;
+        //*/timer.record("isRingerAudible");String inaudibleReason = "";if (!isRingerAudible) {
@@ -774,9 +799,30 @@ public class Ringer {.setShouldRingForContact(shouldRingForContact).setSilentRingingRequested(isSilentRingingRequested).setWorkProfileQuietMode(isWorkProfileInQuietMode)
+                .setForceRing(forceRing).build();}+    //*/ support auto mute media and ring in silent.
+    private boolean shouldForceRingForRepeatCaller(Call call) {
+        if (call == null || call.getHandle() == null) {
+            return false;
+        }
+        final String number = call.getHandle().getSchemeSpecificPart();
+        if (number == null || number.isEmpty()) {
+            return false;
+        }
+        final long currentTime = System.currentTimeMillis();
+        final long expirationTime = currentTime - REPEAT_CALL_WINDOW_MS;
+
+        List<Long> timestamps = mRecentCallHistory.computeIfAbsent(number, k -> new CopyOnWriteArrayList<>());
+        timestamps.removeIf(timestamp -> timestamp < expirationTime);
+        timestamps.add(currentTime);
+        Log.i(this, "Repeat caller check for " + number + ". Call count in last 10 mins: " + timestamps.size());
+        return timestamps.size() >= REPEAT_CALL_THRESHOLD;
+    }
+    //*/
+private boolean isProfileInQuietMode(UserHandle user) {UserManager um = mContext.getSystemService(UserManager.class);return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);

其中 shouldForceRingForRepeatCaller 方法本来是考虑使用电话数据库查询的方式来实现,但原逻辑中存在超时限制,数据库查询耗时会导致 TimeoutException 异常,并且在 getRingerAttributes 方法中不能添加列表的增减操作会导致 ExecutionException 异常。

RingerAttributes 新增变量 mForceRing 和方法 isForceRing() 来控制是否响铃

diff --git a/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingerAttributes.java b/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingerAttributes.java
old mode 100644
new mode 100755
index e0d3e1c..e80a573
--- a/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingerAttributes.java
+++ b/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingerAttributes.java
@@ -26,6 +26,7 @@ public class RingerAttributes {private boolean mShouldRingForContact;private boolean mSilentRingingRequested;private boolean mWorkProfileQuietMode;
+        private boolean mForceRing;public RingerAttributes.Builder setEndEarly(boolean endEarly) {mEndEarly = endEarly;
@@ -67,10 +68,15 @@ public class RingerAttributes {return this;}+        public RingerAttributes.Builder setForceRing(boolean forceRing) {
+            mForceRing = forceRing;
+            return this;
+        }
+public RingerAttributes build() {return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,mRingerAudible, mInaudibleReason, mShouldRingForContact,
-                    mSilentRingingRequested, mWorkProfileQuietMode);
+                    mSilentRingingRequested, mWorkProfileQuietMode, mForceRing);}}@@ -82,11 +88,12 @@ public class RingerAttributes {private boolean mShouldRingForContact;private boolean mSilentRingingRequested;private boolean mWorkProfileQuietMode;
+    private boolean mForceRing;private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,boolean shouldRingForContact, boolean silentRingingRequested,
-            boolean workProfileQuietMode) {
+            boolean workProfileQuietMode, boolean forceRing) {mEndEarly = endEarly;mLetDialerHandleRinging = letDialerHandleRinging;mAcquireAudioFocus = acquireAudioFocus;
@@ -95,6 +102,7 @@ public class RingerAttributes {mShouldRingForContact = shouldRingForContact;mSilentRingingRequested = silentRingingRequested;mWorkProfileQuietMode = workProfileQuietMode;
+        mForceRing = forceRing;}public boolean isEndEarly() {
@@ -128,4 +136,8 @@ public class RingerAttributes {public boolean isWorkProfileInQuietMode() {return mWorkProfileQuietMode;}
+
+    public boolean isForceRing() {
+        return mForceRing;
+    }}

使用 AudioAttributes.USAGE_ALARM 闹钟通道播放铃声

diff --git a/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingtoneFactory.java b/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingtoneFactory.java
index f50d37d..fd96bb8 100755
--- a/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingtoneFactory.java
+++ b/sprdroid14_sys_main/packages/services/Telecomm/src/com/android/server/telecom/RingtoneFactory.java
@@ -72,11 +72,12 @@ public class RingtoneFactory {}public Ringtone getRingtone(Call incomingCall,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) {
+            @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted,
+            boolean forceRing) {// Initializing ringtones on the main thread can deadlockThreadUtil.checkNotOnMainThread();-        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted);
+        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted, forceRing);//Unisoc FL1000060354: Support multi-sim ringtone.Context userHandleContext = getContextForUserHandle(incomingCall.getAssociatedUser());
@@ -141,9 +142,10 @@ public class RingtoneFactory {return ringtone;}-    private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
+    private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted,
+            boolean forceRing) {return new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+            .setUsage(forceRing ? AudioAttributes.USAGE_ALARM : AudioAttributes.USAGE_NOTIFICATION_RINGTONE).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).setHapticChannelsMuted(hapticChannelsMuted).build();
@@ -157,7 +159,7 @@ public class RingtoneFactory {Uri ringtoneUri = Uri.parse("file://" + mContext.getString(com.android.internal.R.string.config_defaultRingtoneVibrationSound));AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
-            /* hapticChannelsMuted */ false);
+            /* hapticChannelsMuted */ false, /* forceRing */ false);Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri, /* volumeShaperConfig */ null, audioAttrs);if (ringtone != null) {
http://www.xdnf.cn/news/19774.html

相关文章:

  • stdexcept介绍与使用指南
  • 【LeetCode】3670. 没有公共位的整数最大乘积 (SOSDP)
  • Day19_【机器学习—线性回归 (3)—回归模型评估方法】
  • Docker一键快速部署压测工具,高效测试 API 接口性能
  • ES6手录01-let与const
  • 学习日记-spring-day47-9.1
  • PyCharm 2025版本中新建python工程文件自动创建.venv的意义和作用
  • 教育 AI 的下半场:个性化学习路径生成背后,技术如何平衡效率与教育本质?
  • 第二十八天-DAC数模转换实验
  • “便农惠农”智慧社区系统(代码+数据库+LW)
  • 【深度学习基础】深度学习中的早停法:从理论到实践的全面解析
  • OpenCV C++ 入门实战:从基础操作到类封装全解析
  • UART控制器——ZYNQ学习笔记14
  • QT中的HTTP
  • GSM8K 原理全解析:从数学推理基准到大模型对齐的试金石
  • 五、练习2:Git分支操作
  • 安卓版 Pad 搭载 OCR 证件识别:酒店入住登记的高效解法
  • 永磁同步电机无速度算法--高频脉振方波注入法(新型位置跟踪策略)
  • Meteor主题友链页面自研
  • QT中的TCP
  • HTML应用指南:利用GET请求获取全国招商银行网点位置信息
  • IS-IS的原理
  • MySQL 性能调优与 SQL 优化的核心利器
  • Windows 命令行:cd 命令1,cd 命令的简单使用
  • 【软件开发工程师の校招秘籍】
  • 安装nodejs安装node.js安装教程(Windows Linux)
  • 盲盒抽谷机小程序开发:如何用3D技术重构沉浸式体验?
  • 闭包的简单讲解
  • LeetCode 19: 删除链表的倒数第 N 个结点
  • 捡捡java——4、日志