最近在看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);
}
}
}
好了我要做的就是讓在靜音模式下繼續可以鬧鐘響鈴,接下來這個排除清單如何執行就交個大神們分析吧。