From 4a35051565b0ce6d29313c3e4ffe19e1dde78db0 Mon Sep 17 00:00:00 2001 From: John Spurlock Date: Tue, 8 Apr 2014 14:08:21 -0400 Subject: [PATCH] App-level notification configuration settings page. New sub-page off of notification settings to ban/unban apps and navigate to an app's notification configuration activity (if configured). Centralized all notification settings artifacts under a new settings subpackage. Bug: 13935172 Change-Id: I53b75c02f0091900734d17dc9217035d0df9b466 --- AndroidManifest.xml | 8 +- res/drawable-hdpi/ic_settings_generic.png | Bin 0 -> 1505 bytes res/drawable-mdpi/ic_settings_generic.png | Bin 0 -> 969 bytes res/drawable-xhdpi/ic_settings_generic.png | Bin 0 -> 2051 bytes res/drawable-xxhdpi/ic_settings_generic.png | Bin 0 -> 2968 bytes res/layout/notification_app.xml | 83 +-- res/layout/notification_app_dialog.xml | 47 ++ res/layout/notification_app_list.xml | 42 ++ res/layout/notification_app_section.xml | 19 + res/layout/notification_info_row.xml | 115 ---- res/layout/notification_log_row.xml | 2 +- res/values/dimens.xml | 5 + res/values/strings.xml | 43 +- res/xml/notification_settings.xml | 87 +-- res/xml/settings_headers.xml | 2 +- .../android/settings/SettingsActivity.java | 4 + .../notification/AppNotificationSettings.java | 550 ++++++++++++++++++ .../NotificationAccessSettings.java | 23 +- .../NotificationSettings.java | 192 +----- .../NotificationStation.java | 11 +- .../{ => notification}/ZenModeSettings.java | 8 +- .../search/SearchIndexableResources.java | 6 +- 22 files changed, 820 insertions(+), 427 deletions(-) create mode 100644 res/drawable-hdpi/ic_settings_generic.png create mode 100644 res/drawable-mdpi/ic_settings_generic.png create mode 100644 res/drawable-xhdpi/ic_settings_generic.png create mode 100644 res/drawable-xxhdpi/ic_settings_generic.png create mode 100644 res/layout/notification_app_dialog.xml create mode 100644 res/layout/notification_app_list.xml create mode 100644 res/layout/notification_app_section.xml delete mode 100644 res/layout/notification_info_row.xml create mode 100644 src/com/android/settings/notification/AppNotificationSettings.java rename src/com/android/settings/{ => notification}/NotificationAccessSettings.java (99%) rename src/com/android/settings/{ => notification}/NotificationSettings.java (55%) rename src/com/android/settings/{ => notification}/NotificationStation.java (99%) rename src/com/android/settings/{ => notification}/ZenModeSettings.java (98%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9a85dc0f9e9..4ee0b217c91 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -564,7 +564,7 @@ + android:value="com.android.settings.notification.ZenModeSettings" /> @@ -772,7 +772,7 @@ + android:value="com.android.settings.notification.NotificationStation" /> - - - - + android:background="?android:attr/listDivider" /> + android:scaleType="center" + android:src="@drawable/ic_settings_generic" /> - + + + \ No newline at end of file diff --git a/res/layout/notification_app_dialog.xml b/res/layout/notification_app_dialog.xml new file mode 100644 index 00000000000..a8f78803bd2 --- /dev/null +++ b/res/layout/notification_app_dialog.xml @@ -0,0 +1,47 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/notification_app_list.xml b/res/layout/notification_app_list.xml new file mode 100644 index 00000000000..9d23a54daf5 --- /dev/null +++ b/res/layout/notification_app_list.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/notification_app_section.xml b/res/layout/notification_app_section.xml new file mode 100644 index 00000000000..628b52452e8 --- /dev/null +++ b/res/layout/notification_app_section.xml @@ -0,0 +1,19 @@ + + diff --git a/res/layout/notification_info_row.xml b/res/layout/notification_info_row.xml deleted file mode 100644 index bc71ef25948..00000000000 --- a/res/layout/notification_info_row.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/notification_log_row.xml b/res/layout/notification_log_row.xml index 284e9ea42b8..702e6b8c31b 100644 --- a/res/layout/notification_log_row.xml +++ b/res/layout/notification_log_row.xml @@ -33,7 +33,7 @@ android:layout_width="@*android:dimen/status_bar_icon_size" android:layout_height="@*android:dimen/status_bar_icon_size" android:layout_centerVertical="true" - android:layout_toEndOf="@id/pkgicon" + android:layout_toEndOf="@+id/pkgicon" android:layout_marginStart="0dp" android:layout_marginEnd="8dp" android:contentDescription="@null" diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3581d6a1cfd..64d4851cb0d 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -78,4 +78,9 @@ 4 6 + + 64dp + 20dp + 4dp + 48dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 09bd6d64b3d..c92bde23f94 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1767,9 +1767,7 @@ Pulse notification light - Heads Up Notifications - - Important notifications will pop up + Pop up if high priority Ringtone @@ -5027,38 +5025,31 @@ - Show when locked + Show on lock screen - Sensitive notification contents will be hidden on the lock screen + Unless content is sensitive - All notification contents will be shown on the lock screen + All notifications + + + Do not disturb - - Limited interruptions - - Configure limited interruptions - Limited Interruptions + Do not disturb Notifications - - General - - - Security - Tweaks - Apps + App notifications @@ -5081,5 +5072,21 @@ NFC tag is not writable. Please use a different tag. + + Default sound + + Loading apps... + + + App notifications + + + Show notifications + + + High priority + + + Done diff --git a/res/xml/notification_settings.xml b/res/xml/notification_settings.xml index 49794225e73..51b153e838d 100644 --- a/res/xml/notification_settings.xml +++ b/res/xml/notification_settings.xml @@ -19,67 +19,44 @@ android:key="notification_settings" xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"> - + - + - + - + - + - - - - - - - - - - - - - - + android:key="manage_notification_access" + android:title="@string/manage_notification_access" + android:persistent="false" + android:fragment="com.android.settings.notification.NotificationAccessSettings" /> diff --git a/res/xml/settings_headers.xml b/res/xml/settings_headers.xml index 73665cdca1e..bf44854f7a1 100644 --- a/res/xml/settings_headers.xml +++ b/res/xml/settings_headers.xml @@ -85,7 +85,7 @@
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index b42f2d0e6eb..4bf0a143560 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -93,6 +93,10 @@ import com.android.settings.inputmethod.UserDictionaryList; import com.android.settings.location.LocationSettings; import com.android.settings.nfc.AndroidBeam; import com.android.settings.nfc.PaymentSettings; +import com.android.settings.notification.NotificationAccessSettings; +import com.android.settings.notification.NotificationSettings; +import com.android.settings.notification.NotificationStation; +import com.android.settings.notification.ZenModeSettings; import com.android.settings.print.PrintJobSettingsFragment; import com.android.settings.print.PrintSettingsFragment; import com.android.settings.tts.TextToSpeechSettings; diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java new file mode 100644 index 00000000000..172557df401 --- /dev/null +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -0,0 +1,550 @@ +/* + * 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.animation.LayoutTransition; +import android.app.AlertDialog; +import android.app.INotificationManager; +import android.app.ListFragment; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.Log; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.SectionIndexer; +import android.widget.TextView; + +import com.android.settings.R; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class AppNotificationSettings extends ListFragment { + private static final String TAG = "AppNotificationSettings"; + private static final boolean DEBUG = true; + + private static final String SECTION_BEFORE_A = "*"; + private static final String SECTION_AFTER_Z = "**"; + private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT + = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_NOTIFICATION_PREFERENCES); + + private final Handler mHandler = new Handler(); + private final ArrayMap mRows = new ArrayMap(); + private final ArrayList mSortedRows = new ArrayList(); + private final ArrayList mSections = new ArrayList(); + + private Context mContext; + private LayoutInflater mInflater; + private NotificationAppAdapter mAdapter; + private Signature[] mSystemSignature; + private Parcelable mListViewState; + private Backend mBackend = new Backend(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = getActivity(); + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mAdapter = new NotificationAppAdapter(mContext); + getActivity().setTitle(R.string.app_notifications_title); + } + + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.notification_app_list, container, false); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + repositionScrollbar(); + getListView().setAdapter(mAdapter); + } + + @Override + public void onPause() { + super.onPause(); + if (DEBUG) Log.d(TAG, "Saving listView state"); + mListViewState = getListView().onSaveInstanceState(); + } + + @Override + public void onResume() { + super.onResume(); + loadAppsList(); + } + + public void setBackend(Backend backend) { + mBackend = backend; + } + + private void loadAppsList() { + AsyncTask.execute(mCollectAppsRunnable); + } + + private String getSection(CharSequence label) { + if (label == null || label.length() == 0) return SECTION_BEFORE_A; + final char c = Character.toUpperCase(label.charAt(0)); + if (c < 'A') return SECTION_BEFORE_A; + if (c > 'Z') return SECTION_AFTER_Z; + return Character.toString(c); + } + + private void repositionScrollbar() { + final int sbWidthPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + getListView().getScrollBarSize(), + getResources().getDisplayMetrics()); + final View parent = (View)getView().getParent(); + final int eat = Math.min(sbWidthPx, parent.getPaddingEnd()); + if (eat <= 0) return; + if (DEBUG) Log.d(TAG, String.format("Eating %dpx into %dpx padding for %dpx scroll, ld=%d", + eat, parent.getPaddingEnd(), sbWidthPx, getListView().getLayoutDirection())); + parent.setPaddingRelative(parent.getPaddingStart(), parent.getPaddingTop(), + parent.getPaddingEnd() - eat, parent.getPaddingBottom()); + } + + private boolean isSystemApp(PackageInfo pkg) { + if (mSystemSignature == null) { + mSystemSignature = new Signature[]{ getSystemSignature() }; + } + return mSystemSignature[0] != null && mSystemSignature[0].equals(getFirstSignature(pkg)); + } + + private static Signature getFirstSignature(PackageInfo pkg) { + if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) { + return pkg.signatures[0]; + } + return null; + } + + private Signature getSystemSignature() { + final PackageManager pm = mContext.getPackageManager(); + try { + final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES); + return getFirstSignature(sys); + } catch (NameNotFoundException e) { + } + return null; + } + + + private void showDialog(final View v, final AppRow row) { + final RelativeLayout layout = (RelativeLayout) + mInflater.inflate(R.layout.notification_app_dialog, null); + final ImageView icon = (ImageView) layout.findViewById(android.R.id.icon); + icon.setImageDrawable(row.icon); + final TextView title = (TextView) layout.findViewById(android.R.id.title); + title.setText(row.label); + final CheckBox showBox = (CheckBox) layout.findViewById(android.R.id.button1); + showBox.setChecked(!row.banned); + final OnCheckedChangeListener showListener = new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + boolean success = mBackend.setNotificationsBanned(row.pkg, row.uid, !isChecked); + if (success) { + row.banned = !isChecked; + mAdapter.bindView(v, row, true /*animate*/); + } else { + showBox.setOnCheckedChangeListener(null); + showBox.setChecked(!isChecked); + showBox.setOnCheckedChangeListener(this); + } + } + }; + showBox.setOnCheckedChangeListener(showListener); + final AlertDialog d = new AlertDialog.Builder(mContext) + .setView(layout) + .setPositiveButton(R.string.app_notifications_dialog_done, null) + .create(); + d.show(); + } + + private static class ViewHolder { + ViewGroup row; + ViewGroup appButton; + ImageView icon; + ImageView banBadge; + ImageView priBadge; + TextView title; + View settingsDivider; + ImageView settingsButton; + View rowDivider; + } + + private class NotificationAppAdapter extends ArrayAdapter implements SectionIndexer { + private final ShapeDrawable mBanShape, mPriShape; + + public NotificationAppAdapter(Context context) { + super(context, 0, 0); + final int s = context.getResources() + .getDimensionPixelSize(R.dimen.notification_app_icon_badge_size); + mBanShape = shape(banPath(s), s); + mPriShape = shape(priPath(s), s); + } + + private ShapeDrawable shape(Path path, int s) { + final ShapeDrawable sd = new ShapeDrawable(new PathShape(path, s, s)); + sd.getPaint().setStyle(Paint.Style.STROKE); + sd.getPaint().setColor(0xffffffff); + sd.getPaint().setStrokeWidth(s / 12); + sd.setIntrinsicWidth(s); + sd.setIntrinsicHeight(s); + return sd; + } + + private Path banPath(int s) { + final Path p = new Path(); + final int d = s / 5; + p.moveTo(d, d); p.lineTo(s - d, s - d); + p.moveTo(d, s - d); p.lineTo(s - d, d); + return p; + } + + private Path priPath(int s) { + final Path p = new Path(); + final int d = s / 5; + p.moveTo(s / 2, d); p.lineTo(s / 2, s - d); + return p; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public int getItemViewType(int position) { + Row r = getItem(position); + return r instanceof AppRow ? 1 : 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Row r = getItem(position); + View v; + if (convertView == null) { + v = newView(parent, r); + } else { + v = convertView; + } + bindView(v, r, false /*animate*/); + return v; + } + + public View newView(ViewGroup parent, Row r) { + if (!(r instanceof AppRow)) { + return mInflater.inflate(R.layout.notification_app_section, parent, false); + } + final View v = mInflater.inflate(R.layout.notification_app, parent, false); + final ViewHolder vh = new ViewHolder(); + vh.row = (ViewGroup) v; + vh.row.setLayoutTransition(new LayoutTransition()); + vh.appButton = (ViewGroup) v.findViewById(android.R.id.button1); + vh.appButton.setLayoutTransition(new LayoutTransition()); + vh.icon = (ImageView) v.findViewById(android.R.id.icon); + vh.banBadge = (ImageView) v.findViewById(android.R.id.icon1); + vh.banBadge.setImageDrawable(mBanShape); + vh.priBadge = (ImageView) v.findViewById(android.R.id.icon2); + vh.priBadge.setImageDrawable(mPriShape); + vh.title = (TextView) v.findViewById(android.R.id.title); + vh.settingsDivider = v.findViewById(R.id.settings_divider); + vh.settingsButton = (ImageView) v.findViewById(android.R.id.button2); + vh.rowDivider = v.findViewById(R.id.row_divider); + v.setTag(vh); + return v; + } + + private void enableLayoutTransitions(ViewGroup vg, boolean enabled) { + if (enabled) { + vg.getLayoutTransition().enableTransitionType(LayoutTransition.APPEARING); + vg.getLayoutTransition().enableTransitionType(LayoutTransition.DISAPPEARING); + } else { + vg.getLayoutTransition().disableTransitionType(LayoutTransition.APPEARING); + vg.getLayoutTransition().disableTransitionType(LayoutTransition.DISAPPEARING); + } + } + + public void bindView(final View view, Row r, boolean animate) { + if (!(r instanceof AppRow)) { + TextView tv = (TextView)view; + tv.setText(r.section); + return; + } + + final AppRow row = (AppRow)r; + final ViewHolder vh = (ViewHolder) view.getTag(); + enableLayoutTransitions(vh.row, animate); + vh.rowDivider.setVisibility(row.first ? View.GONE : View.VISIBLE); + vh.appButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showDialog(view, row); + } + }); + enableLayoutTransitions(vh.appButton, animate); + vh.icon.setImageDrawable(row.icon); + vh.banBadge.setVisibility(row.banned ? View.VISIBLE : View.GONE); + vh.priBadge.setVisibility(row.priority ? View.VISIBLE : View.GONE); + vh.title.setText(row.label); + final boolean showSettings = !row.banned && row.settingsIntent != null; + vh.settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.GONE); + vh.settingsButton.setVisibility(showSettings ? View.VISIBLE : View.GONE); + vh.settingsButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (row.settingsIntent != null) { + getContext().startActivity(row.settingsIntent); + } + } + }); + } + + @Override + public Object[] getSections() { + return mSections.toArray(new Object[mSections.size()]); + } + + @Override + public int getPositionForSection(int sectionIndex) { + final String section = mSections.get(sectionIndex); + final int n = getCount(); + for (int i = 0; i < n; i++) { + final Row r = getItem(i); + if (r.section.equals(section)) { + return i; + } + } + return 0; + } + + @Override + public int getSectionForPosition(int position) { + Row row = getItem(position); + return mSections.indexOf(row.section); + } + } + + private static class Row { + public String section; + } + + private static class AppRow extends Row { + public String pkg; + public int uid; + public Drawable icon; + public CharSequence label; + public Intent settingsIntent; + public boolean banned; + public boolean priority; + public boolean first; + } + + private static final Comparator mRowComparator = new Comparator() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppRow lhs, AppRow rhs) { + return sCollator.compare(lhs.label, rhs.label); + } + }; + + private final Runnable mCollectAppsRunnable = new Runnable() { + @Override + public void run() { + synchronized (mRows) { + final long start = SystemClock.uptimeMillis(); + if (DEBUG) Log.d(TAG, "Collecting apps..."); + mRows.clear(); + mSortedRows.clear(); + + // collect all non-system apps + final PackageManager pm = mContext.getPackageManager(); + for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_SIGNATURES)) { + if (pkg.applicationInfo == null || isSystemApp(pkg)) { + if (DEBUG) Log.d(TAG, "Skipping " + pkg.packageName); + continue; + } + final AppRow row = new AppRow(); + row.pkg = pkg.packageName; + row.uid = pkg.applicationInfo.uid; + try { + row.label = pkg.applicationInfo.loadLabel(pm); + } catch (Throwable t) { + Log.e(TAG, "Error loading application label for " + row.pkg, t); + row.label = row.pkg; + } + row.icon = pkg.applicationInfo.loadIcon(pm); + row.banned = mBackend.getNotificationsBanned(row.pkg, row.uid); + row.priority = mBackend.getHighPriority(row.pkg, row.uid); + mRows.put(row.pkg, row); + } + // collect config activities + Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is " + APP_NOTIFICATION_PREFS_CATEGORY_INTENT); + final List resolveInfos = pm.queryIntentActivities( + APP_NOTIFICATION_PREFS_CATEGORY_INTENT, + PackageManager.MATCH_DEFAULT_ONLY); + if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities"); + for (ResolveInfo ri : resolveInfos) { + final ActivityInfo activityInfo = ri.activityInfo; + final ApplicationInfo appInfo = activityInfo.applicationInfo; + final AppRow row = mRows.get(appInfo.packageName); + if (row == null) { + Log.v(TAG, "Ignoring notification preference activity (" + + activityInfo.name + ") for unknown package " + + activityInfo.packageName); + continue; + } + if (row.settingsIntent != null) { + Log.v(TAG, "Ignoring duplicate notification preference activity (" + + activityInfo.name + ") for package " + + activityInfo.packageName); + continue; + } + row.settingsIntent = new Intent(Intent.ACTION_MAIN) + .setClassName(activityInfo.packageName, activityInfo.name); + } + // sort rows + mSortedRows.addAll(mRows.values()); + Collections.sort(mSortedRows, mRowComparator); + // compute sections + mSections.clear(); + String section = null; + for (AppRow r : mSortedRows) { + r.section = getSection(r.label); + if (!r.section.equals(section)) { + section = r.section; + mSections.add(section); + } + } + mHandler.post(mRefreshAppsListRunnable); + final long elapsed = SystemClock.uptimeMillis() - start; + if (DEBUG) Log.d(TAG, "Collected " + mRows.size() + " apps in " + elapsed + "ms"); + } + } + }; + + private void refreshDisplayedItems() { + if (DEBUG) Log.d(TAG, "Refreshing apps..."); + mAdapter.clear(); + synchronized (mSortedRows) { + String section = null; + final int N = mSortedRows.size(); + boolean first = true; + for (int i = 0; i < N; i++) { + final AppRow row = mSortedRows.get(i); + if (!row.section.equals(section)) { + section = row.section; + Row r = new Row(); + r.section = section; + mAdapter.add(r); + first = true; + } + row.first = first; + mAdapter.add(row); + first = false; + } + } + if (mListViewState != null) { + if (DEBUG) Log.d(TAG, "Restoring listView state"); + getListView().onRestoreInstanceState(mListViewState); + mListViewState = null; + } + if (DEBUG) Log.d(TAG, "Refreshed " + mSortedRows.size() + " displayed items"); + } + + private final Runnable mRefreshAppsListRunnable = new Runnable() { + @Override + public void run() { + refreshDisplayedItems(); + } + }; + + public static class Backend { + public boolean setNotificationsBanned(String pkg, int uid, boolean banned) { + INotificationManager nm = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + try { + nm.setNotificationsEnabledForPackage(pkg, uid, !banned); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean getNotificationsBanned(String pkg, int uid) { + INotificationManager nm = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + try { + final boolean enabled = nm.areNotificationsEnabledForPackage(pkg, uid); + return !enabled; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean getHighPriority(String pkg, int uid) { + // TODO get high-pri state from NoMan + return false; + } + + public boolean setHighPriority(String pkg, int uid, boolean priority) { + // TODO save high-pri state to NoMan + return true; + } + } +} diff --git a/src/com/android/settings/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java similarity index 99% rename from src/com/android/settings/NotificationAccessSettings.java rename to src/com/android/settings/notification/NotificationAccessSettings.java index 07d43537cd4..78ea2d8907a 100644 --- a/src/com/android/settings/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -14,41 +14,42 @@ * limitations under the License. */ -package com.android.settings; +package com.android.settings.notification; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; +import android.app.ListFragment; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; +import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; +import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.util.Slog; -import android.widget.ArrayAdapter; - -import android.app.ListFragment; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Bundle; -import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import com.android.settings.R; + import java.util.HashSet; import java.util.List; diff --git a/src/com/android/settings/NotificationSettings.java b/src/com/android/settings/notification/NotificationSettings.java similarity index 55% rename from src/com/android/settings/NotificationSettings.java rename to src/com/android/settings/notification/NotificationSettings.java index d0b64a61cb3..24863cda183 100644 --- a/src/com/android/settings/NotificationSettings.java +++ b/src/com/android/settings/notification/NotificationSettings.java @@ -14,57 +14,40 @@ * limitations under the License. */ -package com.android.settings; +package com.android.settings.notification; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.database.ContentObserver; -import android.graphics.drawable.Drawable; import android.media.RingtoneManager; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; +import android.preference.TwoStatePreference; import android.provider.Settings; -import android.util.AttributeSet; import android.util.Log; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.SoundSettings; public class NotificationSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener, OnPreferenceClickListener { private static final String TAG = "NotificationSettings"; - private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT - = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_NOTIFICATION_PREFERENCES); - private static final String KEY_NOTIFICATION_SOUND = "notification_sound"; - private static final String KEY_NOTIFICATION_ACCESS = "manage_notification_access"; - private static final String KEY_LOCK_SCREEN_NOTIFICATIONS = "toggle_lock_screen_notifications"; - private static final String KEY_HEADS_UP = "heads_up"; private static final String KEY_NOTIFICATION_PULSE = "notification_pulse"; + private static final String KEY_HEADS_UP = "heads_up"; + private static final String KEY_LOCK_SCREEN_NOTIFICATIONS = "toggle_lock_screen_notifications"; + private static final String KEY_NOTIFICATION_ACCESS = "manage_notification_access"; - private static final String KEY_SECURITY_CATEGORY = "category_security"; - private static final String KEY_APPS_CATEGORY = "category_apps"; private static final String KEY_TWEAKS_CATEGORY = "category_tweaks"; // power toys, eng only private static final int MSG_UPDATE_SOUND_SUMMARY = 2; @@ -74,10 +57,9 @@ public class NotificationSettings extends SettingsPreferenceFragment implements private Preference mNotificationSoundPreference; private Preference mNotificationAccess; - private CheckBoxPreference mLockscreenNotifications; - private CheckBoxPreference mHeadsUp; - private CheckBoxPreference mNotificationPulse; - private PreferenceGroup mAppsPreference; + private TwoStatePreference mLockscreenNotifications; + private TwoStatePreference mHeadsUp; + private TwoStatePreference mNotificationPulse; private final Runnable mRingtoneLookupRunnable = new Runnable() { @Override @@ -104,80 +86,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements } }; - private final ArrayList mAppNotificationInfo - = new ArrayList(); - private final HashSet mAppNotificationInfoPackages = new HashSet(); - private final Comparator mAppComparator = new Comparator() { - private final Collator sCollator = Collator.getInstance(); - @Override - public int compare(AppNotificationInfo lhs, AppNotificationInfo rhs) { - return sCollator.compare(lhs.label, rhs.label); - } - }; - - private final Runnable mCollectAppsRunnable = new Runnable() { - @Override - public void run() { - synchronized (mAppNotificationInfo) { - mAppNotificationInfo.clear(); - mAppNotificationInfoPackages.clear(); - - final PackageManager pm = getPackageManager(); - - final List resolveInfos = pm.queryIntentActivities(APP_NOTIFICATION_PREFS_CATEGORY_INTENT, - PackageManager.MATCH_DEFAULT_ONLY); - - for (ResolveInfo ri : resolveInfos) { - final ActivityInfo activityInfo = ri.activityInfo; - final ApplicationInfo appInfo = activityInfo.applicationInfo; - if (mAppNotificationInfoPackages.contains(activityInfo.packageName)) { - Log.v(TAG, "Ignoring duplicate notification preference activity (" - + activityInfo.name + ") for package " - + activityInfo.packageName); - continue; - } - final AppNotificationInfo info = new AppNotificationInfo(); - mAppNotificationInfoPackages.add(activityInfo.packageName); - - info.label = appInfo.loadLabel(pm); - info.icon = appInfo.loadIcon(pm); - info.name = activityInfo.name; - info.pkg = activityInfo.packageName; - mAppNotificationInfo.add(info); - } - - Collections.sort(mAppNotificationInfo, mAppComparator); - mHandler.post(mRefreshAppsListRunnable); - } - } - }; - - private final Runnable mRefreshAppsListRunnable = new Runnable() { - @Override - public void run() { - synchronized (mAppNotificationInfo) { - mAppsPreference.removeAll(); - Preference p = getPreferenceScreen().findPreference(mAppsPreference.getKey()); - final int N = mAppNotificationInfo.size(); - if (N == 0 && p != null) { - getPreferenceScreen().removePreference(p); - } else if (N > 0 && p == null) { - getPreferenceScreen().addPreference(mAppsPreference); - } - for (int i = 0; i < N; i++) { - final AppNotificationInfo info = mAppNotificationInfo.get(i); - Preference pref = new AppNotificationPreference(mContext); - pref.setTitle(info.label); - pref.setIcon(info.icon); - pref.setIntent(new Intent(Intent.ACTION_MAIN) - .setClassName(info.pkg, info.name)); - mAppsPreference.addPreference(pref); - } - } - } - }; - - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -189,8 +97,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements addPreferencesFromResource(R.xml.notification_settings); final PreferenceScreen root = getPreferenceScreen(); - final PreferenceGroup securityCategory = (PreferenceGroup) - root.findPreference(KEY_SECURITY_CATEGORY); PreferenceGroup tweaksCategory = (PreferenceGroup) root.findPreference(KEY_TWEAKS_CATEGORY); @@ -207,18 +113,16 @@ public class NotificationSettings extends SettingsPreferenceFragment implements refreshNotificationListeners(); mLockscreenNotifications - = (CheckBoxPreference) root.findPreference(KEY_LOCK_SCREEN_NOTIFICATIONS); + = (TwoStatePreference) root.findPreference(KEY_LOCK_SCREEN_NOTIFICATIONS); if (mLockscreenNotifications != null) { if (!getDeviceLockscreenNotificationsEnabled()) { - if (securityCategory != null) { - securityCategory.removePreference(mLockscreenNotifications); - } + root.removePreference(mLockscreenNotifications); } else { mLockscreenNotifications.setChecked(getLockscreenAllowPrivateNotifications()); } } - mHeadsUp = (CheckBoxPreference) findPreference(KEY_HEADS_UP); + mHeadsUp = (TwoStatePreference) findPreference(KEY_HEADS_UP); if (mHeadsUp != null) { updateHeadsUpMode(resolver); mHeadsUp.setOnPreferenceChangeListener(this); @@ -231,7 +135,7 @@ public class NotificationSettings extends SettingsPreferenceFragment implements } }); } - mNotificationPulse = (CheckBoxPreference) findPreference(KEY_NOTIFICATION_PULSE); + mNotificationPulse = (TwoStatePreference) findPreference(KEY_NOTIFICATION_PULSE); if (mNotificationPulse != null && getResources().getBoolean( @@ -246,8 +150,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found"); } } - mAppsPreference = (PreferenceGroup) root.findPreference(KEY_APPS_CATEGORY); - root.removePreference(mAppsPreference); } @Override @@ -256,11 +158,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements refreshNotificationListeners(); lookupRingtoneNames(); - loadAppsList(); - } - - private void loadAppsList() { - AsyncTask.execute(mCollectAppsRunnable); } @Override @@ -330,14 +227,9 @@ public class NotificationSettings extends SettingsPreferenceFragment implements private void refreshNotificationListeners() { if (mNotificationAccess != null) { - final PreferenceGroup securityCategory - = (PreferenceGroup) getPreferenceScreen().findPreference(KEY_SECURITY_CATEGORY); - final int total = NotificationAccessSettings.getListenersCount(mPM); if (total == 0) { - if (securityCategory != null) { - securityCategory.removePreference(mNotificationAccess); - } + getPreferenceScreen().removePreference(mNotificationAccess); } else { final int n = getNumEnabledNotificationListeners(); if (n == 0) { @@ -357,56 +249,4 @@ public class NotificationSettings extends SettingsPreferenceFragment implements private void lookupRingtoneNames() { new Thread(mRingtoneLookupRunnable).start(); } - - // === Per-app notification settings row == - - private static class AppNotificationPreference extends Preference { - private Intent mIntent; - - public AppNotificationPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - setLayoutResource(R.layout.notification_app); - } - - public AppNotificationPreference(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public AppNotificationPreference(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public AppNotificationPreference(Context context) { - this(context, null); - } - - public void setIntent(Intent intent) { - mIntent = intent; - } - - @Override - protected void onBindView(View view) { - super.onBindView(view); - - ImageView icon = (ImageView) view.findViewById(android.R.id.icon); - icon.setImageDrawable(getIcon()); - TextView title = (TextView) view.findViewById(android.R.id.title); - title.setText(getTitle()); - ImageView settingsButton = (ImageView) view.findViewById(android.R.id.button2); - settingsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getContext().startActivity(mIntent); - } - }); - } - } - - private static class AppNotificationInfo { - public Drawable icon; - public CharSequence label; - public String name; - public String pkg; - } } diff --git a/src/com/android/settings/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java similarity index 99% rename from src/com/android/settings/NotificationStation.java rename to src/com/android/settings/notification/NotificationStation.java index 5083e27d810..d8a3efb8dfb 100644 --- a/src/com/android/settings/NotificationStation.java +++ b/src/com/android/settings/notification/NotificationStation.java @@ -14,16 +14,14 @@ * limitations under the License. */ -package com.android.settings; +package com.android.settings.notification; import android.app.Activity; import android.app.ActivityManager; -import android.content.ComponentName; -import android.service.notification.INotificationListener; import android.app.INotificationManager; import android.app.Notification; -import android.service.notification.StatusBarNotification; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -37,6 +35,8 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.service.notification.INotificationListener; +import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -48,6 +48,9 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + import java.util.ArrayList; import java.util.Comparator; import java.util.List; diff --git a/src/com/android/settings/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java similarity index 98% rename from src/com/android/settings/ZenModeSettings.java rename to src/com/android/settings/notification/ZenModeSettings.java index f6c7b8c5c70..de020db66f1 100644 --- a/src/com/android/settings/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.settings; +package com.android.settings.notification; import android.app.ActionBar; import android.app.Activity; import android.content.Context; -import android.database.ContentObserver; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -28,6 +28,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.provider.Settings.Global; +import android.provider.SearchIndexableResource; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; @@ -45,6 +46,9 @@ import android.widget.ScrollView; import android.widget.Switch; import android.widget.TextView; import com.android.settings.search.BaseSearchIndexProvider; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index 6f8efce96f0..af04b5f5282 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -17,27 +17,27 @@ package com.android.settings.search; import android.provider.SearchIndexableResource; + import com.android.settings.DataUsageSummary; import com.android.settings.DateTimeSettings; import com.android.settings.DevelopmentSettings; import com.android.settings.DeviceInfoSettings; import com.android.settings.DisplaySettings; import com.android.settings.HomeSettings; -import com.android.settings.NotificationSettings; import com.android.settings.PrivacySettings; import com.android.settings.R; import com.android.settings.SecuritySettings; import com.android.settings.SoundSettings; import com.android.settings.WallpaperTypeSettings; import com.android.settings.WirelessSettings; -import com.android.settings.ZenModeSettings; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.deviceinfo.Memory; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.location.LocationSettings; -import com.android.settings.net.DataUsageMeteredSettings; +import com.android.settings.notification.NotificationSettings; +import com.android.settings.notification.ZenModeSettings; import com.android.settings.print.PrintSettingsFragment; import com.android.settings.users.UserSettings; import com.android.settings.wifi.WifiSettings;