Android5.0 静音模式下让闹钟仍然响铃





最近在看Android 5.0的代码,发现可以通过音量键来控制情景模式,而且在此次升级的静音模式可谓是完全静音了,就连闹钟都不会发音。

我要做一个可以在静音模式下选择闹钟是否仍然响铃的开关。

本文仅为学习笔记,大神勿喷。



在DeskClock源码中,com.android.deskclock.SettingsActivity.java 中


有如下字段来记录在静音模式下是否响闹钟铃音,不过这个在4.4开始功能就被移除了。


public static final String KEY_ALARM_IN_SILENT_MODE = "alarm_in_silent_mode";


    private void refresh() {
        ....
        //这个在5.0中已经被移除
        final CheckBoxPreference alarmInSilentModePref =
                (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE);
        final int silentModeStreams =
                Settings.System.getInt (getContentResolver(),
                        Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
        alarmInSilentModePref.setChecked(
                (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); 


    }


    是由一个CheckBox开关来控制如下。


    @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
            Preference preference) {
        if (KEY_ALARM_IN_SILENT_MODE.equals(preference.getKey())) {
            CheckBoxPreference pref = (CheckBoxPreference) preference;
            int ringerModeStreamTypes = Settings.System.getInt(
                    getContentResolver(),
                    Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);


            if (pref.isChecked()) {
                ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT;
            } else {
                ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT;
            }


            Settings.System.putInt(getContentResolver(),
                    Settings.System.MODE_RINGER_STREAMS_AFFECTED,
                    ringerModeStreamTypes);


            return true;
        }


        return super.onPreferenceTreeClick(preferenceScreen, preference);
    }
    
    
    //原来这个通过音量键控制的情景模式窗口实在SystemUI中实现的
    com.android.systemui.volume.ZenModePanel.java
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();


        mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
        //在这里创建了三个按钮,接下来看下addButton方法
        mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
        mZenButtons.addButton(R.string.interruption_level_priority,
                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
        mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
        //先注意下这里绑定了一个回调函数
        mZenButtons.setCallback(mZenButtonsCallback);
        ....
    }
    
    接下来看下SegmentedButtons这个extends LinearLayout的自定义控件
    package com.android.systemui.volume.SegmentedButtons.java
    
    public void addButton(int labelResId, Object value) {
        final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false);
        ...
        addView(b);
        //在这里给每个新建的button设置了一个Tag
        b.setTag(value);
        //并注册点击事件
        b.setOnClickListener(mClick);
        Interaction.register(b, new Interaction.Callback() {
            @Override
            public void onInteraction() {
                fireInteraction();
            }
        });
    }
    
    private final View.OnClickListener mClick = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //调用方法将Button的Tag传入
            setSelectedValue(v.getTag());
        }
    };
    
    public void setSelectedValue(Object value) {
        if (Objects.equals(value, mSelectedValue)) return;
        //将Button的Tag传递给mSelectedValue
        mSelectedValue = value;
        ...
        fireOnSelected();
    }
    
        private void fireOnSelected() {
        if (mCallback != null) {
            //在这里用了一个回调
            mCallback.onSelected(mSelectedValue);
        }
    }
    
    回到之前设置回调处继续看
    com.android.systemui.volume.ZenModePanel.java
    
        private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
        @Override
        public void onSelected(Object value) {
            if (value != null && mZenButtons.isShown()) {
                //这里继续将这个标识传递到ZenModeController mController中。
                //然而public interface ZenModeController是个接口
                //实现类在ZenModeControllerImpl.java
                mController.setZen((Integer) value);
            }
        }


        @Override
        public void onInteraction() {
            fireInteraction();
        }
    };
    
    com.android.systemui.statusbar.policy.ZenModeControllerImpl.java
    //在这个对象的构造方法中就有参数Global.ZEN_MODE
    //罗嗦这么多
    //实质就是在数据库中加入一个Global.ZEN_MODE字段来标识状态
    //GlobalSetting mModeSetting;
    public ZenModeControllerImpl(Context context, Handler handler) {
        mContext = context;
        mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
            @Override
            protected void handleValueChanged(int value) {
                fireZenChanged(value);
            }
        };
        ...
    }
    
    public void setZen(int zen) {
        mModeSetting.setValue(zen);
    }
    
    com.android.systemui.qs.GlobalSetting.java
    public abstract class GlobalSetting extends ContentObserver implements Listenable {
        private final Context mContext;
        private final String mSettingName;


        protected abstract void handleValueChanged(int value);


        public GlobalSetting(Context context, Handler handler, String settingName) {
            super(handler);
            mContext = context;
            mSettingName = settingName;
        }
        ...
        public void setValue(int value) {
            Global.putInt(mContext.getContentResolver(), mSettingName, value);
        }
        ...
    }
    
    //既然是给Button状态加监听,那么这个数据库字段的变化也就一定会有人来监听其改变
    //果然在Framework中有监听此字段
    com.android.server.notification.ZenModeHelper.java
    
    private class SettingsObserver extends ContentObserver {
        private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);


        public SettingsObserver(Handler handler) {
            super(handler);
        }


        public void observe() {
            final ContentResolver resolver = mContext.getContentResolver();
            resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this);
            update(null);
        }


        @Override
        public void onChange(boolean selfChange, Uri uri) {
            android.util.Log.i("song","ZenModeHelper -- onChange()");
            update(uri);
        }


        public void update(Uri uri) {
            if (ZEN_MODE.equals(uri)) {
                android.util.Log.i("song","ZenModeHelper -- update()");
                updateZenMode();
            }
        }
    }
    
    public void updateZenMode() {
        android.util.Log.i("song","ZenModeHelper -- updateZenMode()");
        final int mode = Global.getInt(mContext.getContentResolver(),
                Global.ZEN_MODE, Global.ZEN_MODE_OFF);
        if (mode != mZenMode) {
            ZenLog.traceUpdateZenMode(mZenMode, mode);
        }
        mZenMode = mode;
        final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
        //这里有个排除包,猜测就是这里将静音模式下某些应用排除掉,让其可以响铃
        //final String[] exceptionPackages = null; // none (for now)
        //于是注掉上面,给其赋个值,排除掉闹钟试试。
        final String[] exceptionPackages = new String[2];
        exceptionPackages[0] = "com.android.deskclock";


        // call restrictions
        final boolean muteCalls = zen && !mConfig.allowCalls;
        //这里是对振动的控制
        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_NOTIFICATION_RINGTONE,
                muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                exceptionPackages);
        //这里就是对响铃的控制了AppOpsManager mAppOps;
        //继续查看AppOpsManager
        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_NOTIFICATION_RINGTONE,
                muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                exceptionPackages);


        // alarm restrictions
        ///M: Operator customization to mute alarm or not. @{
        final boolean muteAlarms;
        if (mZenModeHelperExt != null) {
            muteAlarms = mZenModeHelperExt
                    .customizeMuteAlarm(mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS);
        } else {
            muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
        }
        ///M: Operator customization to mute alarm or not. @}


        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_ALARM,
                muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                exceptionPackages);
        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_ALARM,
                muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                exceptionPackages);


        // force ringer mode into compliance
        if (mAudioManager != null) {
            int ringerMode = mAudioManager.getRingerMode();
            int forcedRingerMode = -1;
            if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
                if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
                    mPreviousRingerMode = ringerMode;
                    if (DEBUG) Slog.d(TAG, "Silencing ringer");
                    forcedRingerMode = AudioManager.RINGER_MODE_SILENT;
                }
            } else {
                if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
                    if (DEBUG) Slog.d(TAG, "Unsilencing ringer");
                    forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode
                            : AudioManager.RINGER_MODE_NORMAL;
                    mPreviousRingerMode = -1;
                }
            }
            if (forcedRingerMode != -1) {
                mAudioManager.setRingerMode(forcedRingerMode, false /*checkZen*/);
                ZenLog.traceSetRingerMode(forcedRingerMode);
            }
        }
        dispatchOnZenModeChanged();
    }
    
    
    android.app.AppOpsManager.java
    
    //使用了aidl服务
    //它的实现在这里com.android.server.AppOpsService extends IAppOpsService.Stub
    IAppOpsService mService;
    
    public void setRestriction(int code, @AttributeUsage int usage, int mode,
            String[] exceptionPackages) {
            android.util.Log.i("song","AppOpsManager -- setRestriction()");
        try {
            final int uid = Binder.getCallingUid();
            mService.setAudioRestriction(code, usage, uid, mode, exceptionPackages);
        } catch (RemoteException e) {
        }
    }
    
        @Override
    public void setAudioRestriction(int code, int usage, int uid, int mode,
            String[] exceptionPackages) {
        verifyIncomingUid(uid);
        verifyIncomingOp(code);
        synchronized (this) {
            SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code);
            if (usageRestrictions == null) {
                usageRestrictions = new SparseArray<Restriction>();
                mAudioRestrictions.put(code, usageRestrictions);
            }
            usageRestrictions.remove(usage);
            if (mode != AppOpsManager.MODE_ALLOWED) {
                final Restriction r = new Restriction();
                r.mode = mode;
                if (exceptionPackages != null) {
                    final int N = exceptionPackages.length;
                    r.exceptionPackages = new ArraySet<String>(N);
                    for (int i = 0; i < N; i++) {
                        final String pkg = exceptionPackages[i];
                        if (pkg != null) {
                            //最终将他们放入一个排除列表中
                            r.exceptionPackages.add(pkg.trim());
                        }
                    }
                }
                usageRestrictions.put(usage, r);
            }
        }
    }
    
    好了我要做的就是让在静音模式下继续可以闹钟响铃,接下来这个排除列表如何执行就交个大神们分析吧。
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值