主要分析Notification通知聲音流程
一般使用Notification用法如下:
// 建立一個NotificationManager的引用
NotificationManager notificationManager = (NotificationManager)
this.getSystemService(android.content.Context.NOTIFICATION_SERVICE);
// 定義Notification的各種屬性
Notification notification =new Notification(R.drawable.icon,
"督導系統", System.currentTimeMillis());
//FLAG_AUTO_CANCEL 該通知能被狀态欄的清除按鈕給清除掉
//FLAG_NO_CLEAR 該通知不能被狀态欄的清除按鈕給清除掉
//FLAG_ONGOING_EVENT 通知放置在正在運作
//FLAG_INSISTENT 是否一直進行,比如音樂一直播放,知道使用者響應
notification.flags |= Notification.FLAG_ONGOING_EVENT; // 将此通知放到通知欄的"Ongoing"即"正在運作"組中
notification.flags |= Notification.FLAG_NO_CLEAR; // 表明在點選了通知欄中的"清除通知"後,此通知不清除,經常與FLAG_ONGOING_EVENT一起使用
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
//DEFAULT_ALL 使用所有預設值,比如聲音,震動,閃屏等等
//DEFAULT_LIGHTS 使用預設閃光提示
//DEFAULT_SOUNDS 使用預設提示聲音
//DEFAULT_VIBRATE 使用預設手機震動,
//需加上<uses-permission android:name="android.permission.VIBRATE" />權限
notification.defaults = Notification.DEFAULT_LIGHTS;
//疊加效果常量
notification.defaults=Notification.DEFAULT_LIGHTS|Notification.DEFAULT_SOUND;
notification.ledARGB = Color.BLUE;
notification.ledOnMS =; //閃光時間,毫秒
// 設定通知的事件消息
CharSequence contentTitle ="督導系統标題"; // 通知欄标題
CharSequence contentText ="督導系統内容"; // 通知欄内容
Intent notificationIntent =new Intent(MainActivity.this, MainActivity.class); // 點選該通知後要跳轉的Activity
PendingIntent contentItent = PendingIntent.getActivity(this, , notificationIntent, );
notification.setLatestEventInfo(this, contentTitle, contentText, contentItent);
// 把Notification傳遞給NotificationManager
notificationManager.notify(, notification);
給notification設定聲音有2種方式
- notification.defaults 一般是使用這種,使用系統預設聲音
- notification.sound 使用自定義聲音,值為一個聲音檔案的Uri位址
當調用NotificationManager的notify方法,通知才會被觸發
public void notify(String tag, int id, Notification notification)
{
int[] idOut = new int[];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
//如果設定了notification.sound屬性,會檢測該聲音檔案是否可以正常播放
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
//将通知添加棧裡面
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
notification, idOut, UserHandle.myUserId());
if (id != idOut[]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[]);
}
} catch (RemoteException e) {
}
}
代碼第16行,可以看出具體實作是調用NotificationManagerService類的enqueueNotificationWithTag方法來實作的,
// Notifications
// ============================================================================
public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
Notification notification, int[] idOut, int userId)
{
enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), Binder.getCallingPid(),
tag, id, notification, idOut, userId);
}
繼續調用裡面的方法
// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
// uid/pid of another application)
public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId){
//...
//...
mHandler.post(new Runnable() {
@Override
public void run() {
//...
//...
synchronized (mNotificationList) {
// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == )
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != ))
&& (r.getUserId() == UserHandle.USER_ALL ||
(r.getUserId() == userId && r.getUserId() == currentUser))
&& canInterrupt
&& mSystemReady) {
final AudioManager audioManager = (AudioManager) mContext
.getSystemService(Context.AUDIO_SERVICE);
// sound
// should we use the default notification sound? (indicated either by
// DEFAULT_SOUND or because notification.sound is pointing at
// Settings.System.NOTIFICATION_SOUND)
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != ||
Settings.System.DEFAULT_NOTIFICATION_URI
.equals(notification.sound);
Uri soundUri = null;
boolean hasValidSound = false;
//先判斷聲音檔案是否是合法的Uri檔案
if (useDefaultSound) {
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
// check to see if the default notification sound is silent
ContentResolver resolver = mContext.getContentResolver();
hasValidSound = Settings.System.getString(resolver,
Settings.System.NOTIFICATION_SOUND) != null;
} else if (notification.sound != null) {
soundUri = notification.sound;
hasValidSound = (soundUri != null);
}
if (hasValidSound) {
從第220行起,會判斷是否聲音檔案是否是合法檔案
// sound
// should we use the default notification sound? (indicated either by
// DEFAULT_SOUND or because notification.sound is pointing at
// Settings.System.NOTIFICATION_SOUND)
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != ||
Settings.System.DEFAULT_NOTIFICATION_URI
.equals(notification.sound);
Uri soundUri = null;
boolean hasValidSound = false;
//先判斷聲音檔案是否是合法的Uri檔案
if (useDefaultSound) {
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
// check to see if the default notification sound is silent
ContentResolver resolver = mContext.getContentResolver();
hasValidSound = Settings.System.getString(resolver,
Settings.System.NOTIFICATION_SOUND) != null;
} else if (notification.sound != null) {
soundUri = notification.sound;
hasValidSound = (soundUri != null);
}
從代碼裡面看出,先判斷是否使用預設的聲音檔案,如果是,則從SettingProvider裡面擷取聲音檔案uri;如果不使用預設,則通過otification.sound來擷取uri檔案。
如果notification.defaults設定為Notification.DEFAULT_SOUND,則設定notification.sound會沒有效果。是以如果想 notification.sound有效果,則在設定notification.defaults時,不能設定為Notification.DEFAULT_SOUND。
注意,這個Uri檔案不一定存在。
繼續看代碼,第240行
if (hasValidSound) {
boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != ;
int audioStreamType;
if (notification.audioStreamType >= ) {
audioStreamType = notification.audioStreamType;
} else {
audioStreamType = DEFAULT_STREAM_TYPE;
}
mSoundNotification = r;
// do not play notifications if stream volume is (typically because
// ringer mode is silent) or if there is a user of exclusive audio focus
if ((audioManager.getStreamVolume(audioStreamType) != )
&& !audioManager.isAudioFocusExclusive()) {
final long identity = Binder.clearCallingIdentity();
//播放檔案時有一個捕獲異常的操作,如果Uri檔案不存在時,會抛出異常
try {
final IRingtonePlayer player = mAudioService.getRingtonePlayer();
if (player != null) {
player.playAsync(soundUri, user, looping, audioStreamType);
}
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
如果想控制是有通知聲音,這一段代碼提供了思路,可以通過設定通知音量來控制通知是否有聲音。
Anddroid各聲音通道的預設值是
public static final int[] DEFAULT_STREAM_VOLUME = new int[] {
, // STREAM_VOICE_CALL
, // STREAM_SYSTEM
, // STREAM_RING
, // STREAM_MUSIC
, // STREAM_ALARM
, // STREAM_NOTIFICATION
, // STREAM_BLUETOOTH_SCO
, // STREAM_SYSTEM_ENFORCED
, // STREAM_DTMF
, // STREAM_TTS
, // STREAM_FM
// STREAM_MATV
};
Android系統音量鍵響應邏輯
android中将音量的調節分為全局音量(MasterVolume)和個别音量(StreamVolume)。
StreamVolume在靜音後,調節音量大小會從0開始,不會從靜音之前的音量開始調節。
MasterVolume則能夠從靜音之前的音量開始。
如何設定預設使用MasterVolume,還是StreamVolume呢!
在AudioManager對象中有對靜音按鍵的處理:
public void handleKeyDown(KeyEvent event, int stream) {
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
/*
* Adjust the volume in on key down since it is more
* responsive to the user.
*/
int flags = FLAG_SHOW_UI | FLAG_VIBRATE;
if (mUseMasterVolume) {
adjustMasterVolume(
keyCode == KeyEvent.KEYCODE_VOLUME_UP
? ADJUST_RAISE
: ADJUST_LOWER,
flags);
} else {
adjustSuggestedStreamVolume(
keyCode == KeyEvent.KEYCODE_VOLUME_UP
? ADJUST_RAISE
: ADJUST_LOWER,
stream,
flags);
mVolume = getStreamVolume(STREAM_MUSIC);
Log.d(TAG,"mVolume = "+mVolume);
}
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getRepeatCount() == ) {
if (mUseMasterVolume) {
setMasterMute(!isMasterMute());
} else {
// TODO: Actually handle MUTE.
if(getStreamVolume(STREAM_MUSIC) != )
{
mVolume = getStreamVolume(STREAM_MUSIC);
Log.d(TAG,"mVolume = "+mVolume);
setStreamVolume(STREAM_MUSIC, , FLAG_SHOW_UI);
}
else
{
Log.d(TAG,"mVolume = "+mVolume);
setStreamVolume(STREAM_MUSIC, mVolume, FLAG_SHOW_UI);
}
}
}
break;
}
}
mUseMasterVolume控制了使用streamvolume還是mastervolume。
而mUseMasterVolume是從AudioManager的構造函數中初始化的
mUseMasterVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useMasterVolume);
com.android.internal.R.bool.config_useMasterVolume又是什麼呢?是framework的配置資訊。
可以從frameworks/base/core/res/res下的values/config.xml中找到如下一行:
<bool name="config_useMasterVolume">false</bool>
把false改為true即可。
Android修改Notification音量
主要涉及到2個db,internal.db和settings.db:
1.internal.db
檔案位置/data/data/com.android.providers.media/databases/internal.db。裡面儲存了系統預設通知聲音資料,聲音檔案位于system/media/audio/notifications。其中notification是在audio_meta表中的,每個檔案都對應有一個id
該db是在系統啟動時,MediaScanner将掃描到的檔案寫入
2.settings.db
檔案位于data/data/com.android.providers.settings/databases/settings.db.這個db檔案儲存了系統設定的一些預設值。
在system表中,可以看到預設通知聲音檔案:
3.Notification.DEFAULT_SOUND
當一個Notification設定預設聲音為
Notification.DEFAULT_SOUND mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND
系統會讀取預設通知聲音,使用settings.idb裡面的notification_sound值,此值是一個Uri,通過此Uri可以擷取到檔案位置
實用代碼
/**
* get the default notification sound from database ,
* {@link MediaStore.Audio.Media.INTERNAL_CONTENT_URI}
*
* @param context
* @return The default notification sound sound uri
*/
private String getDefaultNotificationSound(Context context) {
String target = DEFAULT_NOTIFICATION_SOUND_URI;
String fileName = SystemProperties.get("ro.config.notification_sound", "");
Log.v(TAG, "ro.config.notification_sound is " + fileName);
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, null, null,
null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
while (cursor != null && cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
if (fileName.equalsIgnoreCase(name)) {
target = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
break;
}
}
if (cursor != null) {
cursor.close();
}
return target;
}
4.使用AudioManager裡面的方法來控制音量
//調節音量加減
adjustStreamVolume(int streamType, int direction, int flags)
//設定音量
setStreamVolume(int streamType, int index, int flags)
//對單獨的streamType設定靜音
setStreamMute(int streamType, boolean state)
a) 其中adjustStreamVolume和setStreamVolume都和前面講到的mUseMasterVolume有關,會擷取此變量影響;
b) setStreamMute與mUseMasterVolume無關,但是與AudioService裡面的mMuteAffectedStreams變量有關
/** @see AudioManager#setStreamMute(int, boolean) */
public void setStreamMute(int streamType, boolean state, IBinder cb) {
if (mUseFixedVolume) {
return;
}
if (isStreamAffectedByMute(streamType)) {
mStreamStates[streamType].mute(cb, state);
}
}
其中mUserFixedVolume也是一個系統配置,一般情況下都不會使用固定值
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
當mUseFixedVolume為false時,會調用isStreamAffectedByMute來判斷目前的streamType是否可以mute
public boolean isStreamAffectedByMute(int streamType) {
return (mMuteAffectedStreams & ( << streamType)) != ;
}
mMuteAffectedStreams 的初始化是在前面的代碼裡面
mMuteAffectedStreams = System.getIntForUser(cr,
System.MUTE_STREAMS_AFFECTED,
(( << AudioSystem.STREAM_MUSIC)|
( << AudioSystem.STREAM_RING)|
( << AudioSystem.STREAM_SYSTEM)),
UserHandle.USER_CURRENT);
System.MUTE_STREAMS_AFFECTED變量是在SettingsProvider裡面的DatabaseHelper類的loadVolumeLevels方法裡面初始化的
loadSetting(stmt, Settings.System.MUTE_STREAMS_AFFECTED,
(( << AudioManager.STREAM_MUSIC) |
( << AudioManager.STREAM_RING) |
( << AudioManager.STREAM_NOTIFICATION) |
( << AudioManager.STREAM_SYSTEM)));
從這一段代碼可以看出,Notification是可以靜音的。
結論是:
audioManager.setStreamMute方法可以控制是否有通知聲音
參考連結:
http://m.blog.csdn.net/blog/Tsunami87/40535957
http://stackoverflow.com/questions/3004713/get-content-uri-from-file-path-in-android
http://stackoverflow.com/questions/20067508/get-real-path-from-uri-android-kitkat-new-storage-access-framework?rq=1
http://www.cnblogs.com/andriod-html5/archive/2011/11/30/2539539.html