/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.notification; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.media.AudioManager; import android.media.RingtoneManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceCategory; import android.preference.SeekBarVolumizer; import android.preference.TwoStatePreference; import android.provider.MediaStore; import android.provider.SearchIndexableResource; import android.provider.Settings; import android.util.Log; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class NotificationSettings extends SettingsPreferenceFragment implements Indexable { private static final String TAG = "NotificationSettings"; private static final String KEY_SOUND = "sound"; private static final String KEY_MEDIA_VOLUME = "media_volume"; private static final String KEY_ALARM_VOLUME = "alarm_volume"; private static final String KEY_RING_VOLUME = "ring_volume"; private static final String KEY_NOTIFICATION_VOLUME = "notification_volume"; private static final String KEY_PHONE_RINGTONE = "ringtone"; private static final String KEY_NOTIFICATION_RINGTONE = "notification_ringtone"; private static final String KEY_VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; private static final String KEY_NOTIFICATION = "notification"; private static final String KEY_NOTIFICATION_PULSE = "notification_pulse"; private static final String KEY_LOCK_SCREEN_NOTIFICATIONS = "lock_screen_notifications"; private static final String KEY_NOTIFICATION_ACCESS = "manage_notification_access"; private static final int SAMPLE_CUTOFF = 2000; // manually cap sample playback at 2 seconds private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback(); private final H mHandler = new H(); private final SettingsObserver mSettingsObserver = new SettingsObserver(); private Context mContext; private PackageManager mPM; private boolean mVoiceCapable; private Preference mPhoneRingtonePreference; private Preference mNotificationRingtonePreference; private TwoStatePreference mVibrateWhenRinging; private TwoStatePreference mNotificationPulse; private DropDownPreference mLockscreen; private Preference mNotificationAccess; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = getActivity(); mPM = mContext.getPackageManager(); mVoiceCapable = Utils.isVoiceCapable(mContext); addPreferencesFromResource(R.xml.notification_settings); final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND); initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC); initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM); if (mVoiceCapable) { initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING); sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME)); } else { initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION); sound.removePreference(sound.findPreference(KEY_RING_VOLUME)); } initRingtones(sound); initVibrateWhenRinging(sound); final PreferenceCategory notification = (PreferenceCategory) findPreference(KEY_NOTIFICATION); initPulse(notification); initLockscreenNotifications(notification); mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS); refreshNotificationListeners(); } @Override public void onResume() { super.onResume(); refreshNotificationListeners(); lookupRingtoneNames(); mSettingsObserver.register(true); } @Override public void onPause() { super.onPause(); mVolumeCallback.stopSample(); mSettingsObserver.register(false); } // === Volumes === private void initVolumePreference(String key, int stream) { final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key); volumePref.setStream(stream); volumePref.setCallback(mVolumeCallback); } 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 stopSample() { if (mCurrent != null) { mCurrent.stopSample(); } } }; // === Phone & notification ringtone === private void initRingtones(PreferenceCategory root) { mPhoneRingtonePreference = root.findPreference(KEY_PHONE_RINGTONE); if (mPhoneRingtonePreference != null && !mVoiceCapable) { root.removePreference(mPhoneRingtonePreference); mPhoneRingtonePreference = null; } mNotificationRingtonePreference = root.findPreference(KEY_NOTIFICATION_RINGTONE); } private void lookupRingtoneNames() { AsyncTask.execute(mLookupRingtoneNames); } private final Runnable mLookupRingtoneNames = new Runnable() { @Override public void run() { if (mPhoneRingtonePreference != null) { final CharSequence summary = updateRingtoneName( mContext, RingtoneManager.TYPE_RINGTONE); if (summary != null) { mHandler.obtainMessage(H.UPDATE_PHONE_RINGTONE, summary).sendToTarget(); } } if (mNotificationRingtonePreference != null) { final CharSequence summary = updateRingtoneName( mContext, RingtoneManager.TYPE_NOTIFICATION); if (summary != null) { mHandler.obtainMessage(H.UPDATE_NOTIFICATION_RINGTONE, summary).sendToTarget(); } } } }; private static CharSequence updateRingtoneName(Context context, int type) { if (context == null) { Log.e(TAG, "Unable to update ringtone name, no context provided"); return null; } Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown); // Is it a silent ringtone? if (ringtoneUri == null) { summary = context.getString(com.android.internal.R.string.ringtone_silent); } else { // Fetch the ringtone title from the media provider Cursor cursor = null; try { cursor = context.getContentResolver().query(ringtoneUri, new String[] { MediaStore.Audio.Media.TITLE }, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { summary = cursor.getString(0); } } } catch (SQLiteException sqle) { // Unknown title for the ringtone } finally { if (cursor != null) { cursor.close(); } } } return summary; } // === Vibrate when ringing === private void initVibrateWhenRinging(PreferenceCategory root) { mVibrateWhenRinging = (TwoStatePreference) root.findPreference(KEY_VIBRATE_WHEN_RINGING); if (mVibrateWhenRinging == null) { Log.i(TAG, "Preference not found: " + KEY_VIBRATE_WHEN_RINGING); return; } if (!mVoiceCapable) { root.removePreference(mVibrateWhenRinging); mVibrateWhenRinging = null; return; } mVibrateWhenRinging.setPersistent(false); updateVibrateWhenRinging(); mVibrateWhenRinging.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean val = (Boolean) newValue; return Settings.System.putInt(getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, val ? 1 : 0); } }); } private void updateVibrateWhenRinging() { if (mVibrateWhenRinging == null) return; mVibrateWhenRinging.setChecked(Settings.System.getInt(getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 0) != 0); } // === Pulse notification light === private void initPulse(PreferenceCategory parent) { mNotificationPulse = (TwoStatePreference) parent.findPreference(KEY_NOTIFICATION_PULSE); if (mNotificationPulse == null) { Log.i(TAG, "Preference not found: " + KEY_NOTIFICATION_PULSE); return; } if (!getResources() .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) { parent.removePreference(mNotificationPulse); } else { updatePulse(); mNotificationPulse.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean val = (Boolean)newValue; return Settings.System.putInt(getContentResolver(), Settings.System.NOTIFICATION_LIGHT_PULSE, val ? 1 : 0); } }); } } private void updatePulse() { if (mNotificationPulse == null) { return; } try { mNotificationPulse.setChecked(Settings.System.getInt(getContentResolver(), Settings.System.NOTIFICATION_LIGHT_PULSE) == 1); } catch (Settings.SettingNotFoundException snfe) { Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found"); } } // === Lockscreen (public / private) notifications === private void initLockscreenNotifications(PreferenceCategory parent) { mLockscreen = (DropDownPreference) parent.findPreference(KEY_LOCK_SCREEN_NOTIFICATIONS); if (mLockscreen == null) { Log.i(TAG, "Preference not found: " + KEY_LOCK_SCREEN_NOTIFICATIONS); return; } mLockscreen.addItem(R.string.lock_screen_notifications_summary_show, R.string.lock_screen_notifications_summary_show); mLockscreen.addItem(R.string.lock_screen_notifications_summary_hide, R.string.lock_screen_notifications_summary_hide); mLockscreen.addItem(R.string.lock_screen_notifications_summary_disable, R.string.lock_screen_notifications_summary_disable); updateLockscreenNotifications(); mLockscreen.setCallback(new DropDownPreference.Callback() { @Override public boolean onItemSelected(int pos, Object value) { final int val = (Integer) value; final boolean enabled = val != R.string.lock_screen_notifications_summary_disable; final boolean show = val == R.string.lock_screen_notifications_summary_show; Settings.Secure.putInt(getContentResolver(), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, show ? 1 : 0); Settings.Global.putInt(getContentResolver(), Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, enabled ? 1 : 0); return true; } }); } private void updateLockscreenNotifications() { if (mLockscreen == null) { return; } final boolean allowPrivate = getLockscreenAllowPrivateNotifications(); final boolean enabled = getLockscreenNotificationsEnabled(); final int selectedVal = !enabled ? R.string.lock_screen_notifications_summary_disable : allowPrivate ? R.string.lock_screen_notifications_summary_show : R.string.lock_screen_notifications_summary_hide; mLockscreen.setSelectedValue(selectedVal); } private boolean getLockscreenNotificationsEnabled() { return Settings.Global.getInt(getContentResolver(), Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; } private boolean getLockscreenAllowPrivateNotifications() { return Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; } // === Notification listeners === private void refreshNotificationListeners() { if (mNotificationAccess != null) { final int total = NotificationAccessSettings.getListenersCount(mPM); if (total == 0) { getPreferenceScreen().removePreference(mNotificationAccess); } else { final int n = NotificationAccessSettings.getEnabledListenersCount(mContext); if (n == 0) { mNotificationAccess.setSummary(getResources().getString( R.string.manage_notification_access_summary_zero)); } else { mNotificationAccess.setSummary(String.format(getResources().getQuantityString( R.plurals.manage_notification_access_summary_nonzero, n, n))); } } } } // === Callbacks === private final class SettingsObserver extends ContentObserver { private final Uri VIBRATE_WHEN_RINGING_URI = Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING); private final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); private final Uri LOCK_SCREEN_PRIVATE_URI = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); private final Uri LOCK_SCREEN_SHOW_URI = Settings.Global.getUriFor(Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS); public SettingsObserver() { super(mHandler); } public void register(boolean register) { final ContentResolver cr = getContentResolver(); if (register) { cr.registerContentObserver(VIBRATE_WHEN_RINGING_URI, false, this); cr.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this); cr.registerContentObserver(LOCK_SCREEN_PRIVATE_URI, false, this); cr.registerContentObserver(LOCK_SCREEN_SHOW_URI, false, this); } else { cr.unregisterContentObserver(this); } } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); if (VIBRATE_WHEN_RINGING_URI.equals(uri)) { updateVibrateWhenRinging(); } if (NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { updatePulse(); } if (LOCK_SCREEN_PRIVATE_URI.equals(uri) || LOCK_SCREEN_SHOW_URI.equals(uri)) { updateLockscreenNotifications(); } } } private final class H extends Handler { private static final int UPDATE_PHONE_RINGTONE = 1; private static final int UPDATE_NOTIFICATION_RINGTONE = 2; private static final int STOP_SAMPLE = 3; private H() { super(Looper.getMainLooper()); } @Override public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_PHONE_RINGTONE: mPhoneRingtonePreference.setSummary((CharSequence) msg.obj); break; case UPDATE_NOTIFICATION_RINGTONE: mNotificationRingtonePreference.setSummary((CharSequence) msg.obj); break; case STOP_SAMPLE: mVolumeCallback.stopSample(); break; } } } // === Indexing === public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { public List getXmlResourcesToIndex( Context context, boolean enabled) { final SearchIndexableResource sir = new SearchIndexableResource(context); sir.xmlResId = R.xml.notification_settings; return Arrays.asList(sir); } public List getNonIndexableKeys(Context context) { final ArrayList rt = new ArrayList(); if (Utils.isVoiceCapable(context)) { rt.add(KEY_NOTIFICATION_VOLUME); } else { rt.add(KEY_RING_VOLUME); rt.add(KEY_PHONE_RINGTONE); rt.add(KEY_VIBRATE_WHEN_RINGING); } return rt; } }; }