Android6.0 源码修改之Settings音量调节界面增加通话音量调节

前言

今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了。需要优化两个地方

1、在正常情况下,按物理音量加减键都显示 通话音量调节seekBar,可方便快速调节通话音量

2、在Settings中提示音界面点击设置进入,增加通话音量调节seekBar


修改前


修改后

实现

第一个功能

先来完成第一个功能,还是通过Hierarchy View查看布局结构,查找到布局文件id为volume_dialog,通过在源码中搜索找到位于SystemUI中,volume_dialog.xml
源码位置 frameworks\base\packages\SystemUI\res\layout\volume_dialog.xml

对应的java类为 frameworks\base\packages\SystemUI\src\com\android\systemui\volume\VolumeDialog.java

修改代码

addRow(AudioManager.STREAM_VOICE_CALL,R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, true);

原来的第四个参数为false,修改为true即可显示通话音量seekBar

为了便于说明,我们跟进addRow()中查看

private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important);if (!mRows.isEmpty()) {final View v = new View(mContext);v.setId(android.R.id.background);final int h = mContext.getResources().getDimensionPixelSize(R.dimen.volume_slider_interspacing);final LinearLayout.LayoutParams lp =new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);mDialogContentView.addView(v, mDialogContentView.getChildCount() - 1, lp);row.space = v;}...
}

传递的参数对应important,从字面意思理解重要对应显示,继续查看initRow都做了什么

private VolumeRow initRow(final int stream, int iconRes, int iconMuteRes, boolean important) {final VolumeRow row = new VolumeRow();row.stream = stream;row.iconRes = iconRes;row.iconMuteRes = iconMuteRes;row.important = important;row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);row.view.setTag(row);row.header = (TextView) row.view.findViewById(R.id.volume_row_header);mSpTexts.add(row.header);row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));// forward events above the slider into the sliderrow.view.setOnTouchListener(new OnTouchListener() {private final Rect mSliderHitRect = new Rect();private boolean mDragging;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {row.slider.getHitRect(mSliderHitRect);if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN&& event.getY() < mSliderHitRect.top) {mDragging = true;}if (mDragging) {event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top);row.slider.dispatchTouchEvent(event);if (event.getActionMasked() == MotionEvent.ACTION_UP|| event.getActionMasked() == MotionEvent.ACTION_CANCEL) {mDragging = false;}return true;}return false;}});row.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon);row.icon.setImageResource(iconRes);row.icon.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState);mController.setActiveStream(row.stream);if (row.stream == AudioManager.STREAM_RING) {final boolean hasVibrator = mController.hasVibrator();if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {if (hasVibrator) {mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);} else {final boolean wasZero = row.ss.level == 0;mController.setStreamVolume(stream, wasZero ? row.lastAudibleLevel : 0);}} else {mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);if (row.ss.level == 0) {mController.setStreamVolume(stream, 1);}}} else {final boolean vmute = row.ss.level == 0;mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);}row.userAttempt = 0;  // reset the grace period, slider should update immediately}});row.settingsButton = (ImageButton) row.view.findViewById(R.id.volume_settings_button);row.settingsButton.setOnClickListener(mClickSettings);return row;
}

从上面可看出,将一些变量都保存到了VolumeRow中,设置了icon的点击事件,将当前对应的音量类型设置为最低(禁音), 设置seekBar的改变事件。通过过滤日志,查找到控制音量类型的显示和隐藏的代码块updateRowsH()

private boolean isVisibleH(VolumeRow row, boolean isActive) {return mExpanded && row.view.getVisibility() == View.VISIBLE|| (mExpanded && (row.important || isActive))|| !mExpanded && isActive;
}private void updateRowsH() {if (D.BUG) Log.d(TAG, "updateRowsH");final VolumeRow activeRow = getActiveRow();updateFooterH();updateExpandButtonH();if (!mShowing) {trimObsoleteH();}// apply changes to all rowsfor (VolumeRow row : mRows) {final boolean isActive = row == activeRow;final boolean visible = isVisibleH(row, isActive);Log.e(TAG, "row==" + row.stream + " isActive=="+isActive + " visible="+visible);Util.setVisOrGone(row.view, visible);Util.setVisOrGone(row.space, visible && mExpanded);final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0;if (expandButtonRes != row.cachedExpandButtonRes) {row.cachedExpandButtonRes = expandButtonRes;if (expandButtonRes == 0) {row.settingsButton.setImageDrawable(null);} else {row.settingsButton.setImageResource(expandButtonRes);}}Util.setVisOrInvis(row.settingsButton, false);updateVolumeRowHeaderVisibleH(row);row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f);updateVolumeRowSliderTintH(row, isActive);}
}

遍历已经添加的音量类型集合mRows,依次判断是否处于活动状态,再和开始设置的important属性比较。mExpanded是否展开,默认只显示铃声音量控制,点击下拉的按钮,才完全显示其它的音量控制

mExpanded && row.view.getVisibility() == View.VISIBLE || (mExpanded && (row.important || isActive)) || !mExpanded && isActive

true && false || (true && (true || false)) || false && true —>true

好了,至此分析完毕,重新mmm push SystemUI.apk 查看效果

第二个功能

源码位置

Settings\res_ext\xml\edit_profile_prefs.xml
Settings\src\com\mediatek\audioprofile\Editprofile.java
Settings\src\com\mediatek\audioprofile\VolumeSeekBarPreference.java

在edit_profile_prefs.xml中仿照原来的Alarm volume和Ring volume,新增加一个Call volume

<!-- Media volume -->
<com.mediatek.audioprofile.VolumeSeekBarPreferenceandroid:key="media_volume"android:icon="@*android:drawable/ic_audio_vol"android:title="@string/media_volume_option_title" /><!-- Alarm volume -->
<com.mediatek.audioprofile.VolumeSeekBarPreferenceandroid:key="alarm_volume"android:icon="@*android:drawable/ic_audio_alarm"android:title="@string/alarm_volume_option_title" /><!-- Ring volume -->
<com.mediatek.audioprofile.VolumeSeekBarPreferenceandroid:key="ring_volume"android:icon="@*android:drawable/ic_audio_ring_notif"android:title="@string/ring_volume_option_title" /><!-- Call volume -->
<com.mediatek.audioprofile.VolumeSeekBarPreferenceandroid:key="call_volume"android:icon="@drawable/ic_volume_voice"android:title="@string/call_volume_option_title" />

对应的drawable文件时从SystemUI中拷贝过来的,ic_volume_voice.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" ><pathandroid:fillColor="#ff727272"android:pathData="M13.25,21.59c2.88,5.66 7.51,10.29 13.18,13.17l4.4,-4.41c0.55,-0.55 1.34,-0.71 2.03,-0.49C35.1,30.6 37.51,31.0 40.0,31.0c1.11,0.0 2.0,0.89 2.0,2.0l0.0,7.0c0.0,1.11 -0.89,2.0 -2.0,2.0C21.22,42.0 6.0,26.78 6.0,8.0c0.0,-1.1 0.9,-2.0 2.0,-2.0l7.0,0.0c1.11,0.0 2.0,0.89 2.0,2.0 0.0,2.4 0.4,4.9 1.14,7.1 0.2,0.6 0.06,1.48 -0.49,2.03l-4.4,4.42z" /></vector>

接下来对应到 Editprofile.java 文件中,可以看到 KEY_ALARM_VOLUME 对应的preference初始化,依旧照葫芦画瓢,添加 KEY_CALL_VOLUME

private void initVolume(PreferenceScreen parent) {initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC);initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM);initVolumePreference(KEY_CALL_VOLUME, AudioManager.STREAM_VOICE_CALL);if (mVoiceCapable) {mVolume = initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING);parent.removePreference(parent.findPreference(KEY_NOTIFICATION_VOLUME));} else {mVolume = initVolumePreference(KEY_NOTIFICATION_VOLUME,AudioManager.STREAM_NOTIFICATION);parent.removePreference(parent.findPreference(KEY_RING_VOLUME));}
}

重新编译,push替换后发现,UI倒是出来了,但是无法滑动,事情果然没那么简单,继续查看 initVolumePreference()

private VolumeSeekBarPreference initVolumePreference(String key, int stream) {Log.d("@M_" + TAG, "Init volume preference, key = " + key + ",stream = " + stream);final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key);volumePref.setStream(stream);volumePref.setCallback(mVolumeCallback);volumePref.setProfile(mKey);return volumePref;
}

保存了当前的音量调节类型,设置seekBar回调事件,接下来看看回调处理了什么

private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {private SeekBarVolumizer mCurrent;@Overridepublic void onSampleStarting(SeekBarVolumizer sbv) {if (mCurrent != null && mCurrent != sbv) {mCurrent.stopSample();}mCurrent = sbv;if (mCurrent != null) {mHandler.removeMessages(H.STOP_SAMPLE);mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF);}}public void onStreamValueChanged(int stream, int progress) {if (stream == AudioManager.STREAM_RING) {mHandler.removeMessages(H.UPDATE_RINGER_ICON);mHandler.obtainMessage(H.UPDATE_RINGER_ICON, progress, 0).sendToTarget();}}public void stopSample() {if (mCurrent != null) {mCurrent.stopSample();}}public void ringtoneChanged() {if (mCurrent != null) {mCurrent.ringtoneChanged();} else {mVolume.getSeekBar().ringtoneChanged();}}
};

当我们点击或者是滑动seekBar时,会根据当前设置的音量大小播放一段短暂的默认铃音,当铃音未播放完成时,再次点击将不进行播放。继续跟进 VolumeSeekBarPreference.java 中

@Override
protected void onBindView(View view) {super.onBindView(view);if (mStream == 0) {Log.w(TAG, "No stream found, not binding volumizer  ");return;}getPreferenceManager().registerOnActivityStopListener(this);final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);if (seekBar == mSeekBar) {return;}mSeekBar = seekBar;final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() {@Overridepublic void onSampleStarting(SeekBarVolumizer sbv) {if (mCallback != null) {mCallback.onSampleStarting(sbv);}}};final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;if (mVolumizer == null) {mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc, mKey);}//mVolumizer.setProfile(mKey);mVolumizer.setSeekBar(mSeekBar);
}

mStream == 0, 直接return,会不会和这有关系呢,来看下各个音量调节类型对应的int值

/** Used to identify the volume of audio streams for phone calls */
public static final int STREAM_VOICE_CALL = 0;
/** Used to identify the volume of audio streams for the phone ring and message alerts */
public static final int STREAM_RING = 2;
/** Used to identify the volume of audio streams for music playback */
public static final int STREAM_MUSIC = 3;
/** Used to identify the volume of audio streams for alarms */
public static final int STREAM_ALARM = 4;

我们新增的 STREAM_VOICE_CALL对应的mStream正好为0,直接给return掉了,所以修改为 mStream < 0 即可,重新编译 push 发现成功了。

具体的音量调节逻辑在 packages\apps\Settings\src\com\mediatek\audioprofile\SeekBarVolumizer.java 中,感兴趣的可继续深究,肯定离不开调用 .setStreamVolume()方法,大概看了一眼,主要是onProgressChanged()回调,通过postSetVolume(progress)方法,发送MSG_SET_STREAM_VOLUME消息,最终调用saveVolume()

Android6.0 源码修改之Settings音量调节界面增加通话音量调节相关推荐

  1. Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮...

    Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮 前言 之前写过屏蔽系统导航栏功能的文章,具体可看Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP) ...

  2. Android7.1 源码修改之Settings音量调节界面增加通话音量调节

    前言 今天产品要求在Settings中添加调节电话音量的功能,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就 ...

  3. Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar...

    场景分析, 为了完全实现沉浸式效果,在进入特定的app后可以将导航栏移除,当退出app后再次将导航栏恢复.(下面将采用发送广播的方式来移除和恢复导航栏) ps:不修改源码的情况下,简单的沉浸式效果实现 ...

  4. 在Ubuntu Server14.04上编译Android6.0源码

    此前编译过Android4.4的源码,但是现在Android都到了7.0的版本,不禁让我感叹Google的步伐真心难跟上,趁这周周末时间比较充裕,于是在过去的24小时里,毅然花了9个小时编译了一把An ...

  5. Android6.0 源码添加黑名单拦截电话和短信记录

    [目标] 在上一篇 Android6.0 源码增加黑名单功能 的基础上增加黑名单和短信拦截记录 [实现] 黑名单数据库和拦截记录数据库上一篇已经增加完成,这就需要我们在电话和短信分发的地方去判断号码是 ...

  6. Android源码定制(1)——Android6.0源码编译

    一.前言 最近在研究Xposed框架定制,恰好又看到看雪上两个大佬关于源码定制和Xposed源码定制的帖子,所以尝试基于Android6.0版本,详细记录一下从源码下载到Xposed框架定制的全过程. ...

  7. android6.0源码分析之AMS服务源码分析

    activitymanagerservice服务源码分析 1.ActivityManagerService概述 ActivityManagerService(以下简称AMS)作为Android中最核心 ...

  8. android6.0源码分析之Zygote进程分析

    在android6.0源码分析之Runtime的初始化一文中,对Zygote进程的初期的Runtime初始化过程进行了分析,在Runtime启动结束后,会对Zygote进程进行初始化,其它Java进程 ...

  9. android6.0源码分析之Runtime的初始化

    Android运行时作为android架构的一部分,起着非常重要的作用,它和核心库(Core Libraries)组成了Android运行时库层.本文将依据android源码对AndroidRunti ...

最新文章

  1. Gym 100883J palprime(二分判断点在凸包里)
  2. 关于Resin SSL支持的两个问题
  3. 九 Deepin配置ssh访问gitee
  4. centos5.4 安装配置oracle10g
  5. centos加单个ip和批量添加
  6. [react] 说说你对windowing的了解
  7. 【华为云技术分享】一统江湖大前端DOClever—你的Postman有点Low
  8. mysql5.7.23主主重制_MySQL5.7.23,主从复制的使用
  9. python模块与类的区别_Python类、模块、包的概念及区别
  10. [Linux] PHP程序员玩转Linux系列-自动备份与SVN
  11. 课堂作业04 2017.10.27
  12. sql2000 mysql_sql2000迷你版 超精简版SQL Server 2000数据库下载
  13. 什么款式蓝牙耳机玩游戏更舒适?五款小巧低延迟蓝牙耳机推荐
  14. IT界最伟大的十位人物
  15. 强烈推荐这 15 个网站!
  16. Vue3 - Suspense 组件介绍及使用方法
  17. 操作系统的分类有哪些?
  18. 免费的.cn域名,我的新域名
  19. android轻音乐,「睡眠周期时钟」搭配轻音乐,让你好好睡又舒服醒(Android)
  20. esc中文是什么意思_汽车ESC什么意思 汽车ESC有什么用

热门文章

  1. HDU 1.3.3 开门人和关门人
  2. 离散数学-数理逻辑-命题逻辑的基本概念(1)
  3. GSM Communication on EBox4300--(2)
  4. unity科技风UI界面
  5. python 文本词汇,句子校正 autocorrect库的使用
  6. JS小数点保留后2位
  7. 基于微信在线考试小程序系统设计与实现 开题报告
  8. Netty - 探究PageCache磁盘高速缓存
  9. C4D的GPU渲染器Octane和Redshift的渲染对比
  10. 小白安装linux系统-u盘安装lubuntu