天天看點

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 , 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 );

                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);

            }

        }

    }

    好了我要做的就是讓在靜音模式下繼續可以鬧鐘響鈴,接下來這個排除清單如何執行就交個大神們分析吧。

繼續閱讀