欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-07-09 14:48:45
前言 今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了。需要优化两个地方 1、在正常情况下,按物理音量加减键都显示 通话音量调节seekBar,可方便快速调节通话音量 2、在Settin ......

前言

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

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

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

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

修改前

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

修改后

实现

第一个功能

先来完成第一个功能,还是通过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 slider
    row.view.setontouchlistener(new ontouchlistener() {
        private final rect msliderhitrect = new rect();
        private boolean mdragging;

        @suppresslint("clickableviewaccessibility")
        @override
        public 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() {
        @override
        public 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 rows
    for (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.volumeseekbarpreference
        android:key="media_volume"
        android:icon="@*android:drawable/ic_audio_vol"
        android:title="@string/media_volume_option_title" />

<!-- alarm volume -->
<com.mediatek.audioprofile.volumeseekbarpreference
        android:key="alarm_volume"
        android:icon="@*android:drawable/ic_audio_alarm"
        android:title="@string/alarm_volume_option_title" />

<!-- ring volume -->
<com.mediatek.audioprofile.volumeseekbarpreference
        android:key="ring_volume"
        android:icon="@*android:drawable/ic_audio_ring_notif"
        android:title="@string/ring_volume_option_title" />

<!-- call volume -->
<com.mediatek.audioprofile.volumeseekbarpreference
        android: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" >

<path
    android: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;

    @override
    public 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() {
        @override
        public 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()