summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/settings/Settings.java1
-rw-r--r--src/com/android/settings/SettingsActivity.java4
-rw-r--r--src/com/android/settings/notification/AppNotificationDialog.java180
-rw-r--r--src/com/android/settings/notification/AppNotificationSettings.java643
-rw-r--r--src/com/android/settings/notification/NotificationAppList.java569
5 files changed, 709 insertions, 688 deletions
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 177bcf8..123d4fe 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -92,6 +92,7 @@ public class Settings extends SettingsActivity {
public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class OtherSoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class QuickLaunchSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 520bfba..f4f34bc 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -82,7 +82,7 @@ import com.android.settings.deviceinfo.Memory;
import com.android.settings.deviceinfo.UsbSettings;
import com.android.settings.fuelgauge.BatterySaverSettings;
import com.android.settings.fuelgauge.PowerUsageSummary;
-import com.android.settings.notification.AppNotificationSettings;
+import com.android.settings.notification.NotificationAppList;
import com.android.settings.notification.OtherSoundSettings;
import com.android.settings.quicklaunch.QuickLaunchSettings;
import com.android.settings.search.DynamicIndexableContentMonitor;
@@ -94,6 +94,7 @@ 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.AppNotificationSettings;
import com.android.settings.notification.ConditionProviderSettings;
import com.android.settings.notification.NotificationAccessSettings;
import com.android.settings.notification.NotificationSettings;
@@ -286,6 +287,7 @@ public class SettingsActivity extends Activity
ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
InstalledAppDetails.class.getName(),
BatterySaverSettings.class.getName(),
+ NotificationAppList.class.getName(),
AppNotificationSettings.class.getName(),
OtherSoundSettings.class.getName(),
QuickLaunchSettings.class.getName()
diff --git a/src/com/android/settings/notification/AppNotificationDialog.java b/src/com/android/settings/notification/AppNotificationDialog.java
deleted file mode 100644
index 55f25c2..0000000
--- a/src/com/android/settings/notification/AppNotificationDialog.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
-import com.android.settings.R;
-import com.android.settings.notification.AppNotificationSettings.Backend;
-import com.android.settings.notification.AppNotificationSettings.AppRow;
-
-public class AppNotificationDialog extends AlertActivity {
- private static final String TAG = "AppNotificationDialog";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * Show a checkbox in the per-app notification control dialog to allow the user to
- * selectively redact this app's notifications on the lockscreen.
- */
- private static final boolean ENABLE_APP_NOTIFICATION_PRIVACY_OPTION = false;
-
- private final Context mContext = this;
- private final Backend mBackend = new Backend();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + getIntent());
- if (!buildDialog()) {
- Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show();
- finish();
- }
- }
-
- private boolean buildDialog() {
- final Intent intent = getIntent();
- if (intent != null) {
- final int uid = intent.getIntExtra(Settings.EXTRA_APP_UID, -1);
- final String pkg = intent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
- if (uid != -1 && !TextUtils.isEmpty(pkg)) {
- if (DEBUG) Log.d(TAG, "Load details for pkg=" + pkg + " uid=" + uid);
- final PackageManager pm = getPackageManager();
- final PackageInfo info = findPackageInfo(pm, pkg, uid);
- if (info != null) {
- final AppRow row = AppNotificationSettings.loadAppRow(pm, info, mBackend);
- final AlertController.AlertParams p = mAlertParams;
- p.mView = getLayoutInflater().inflate(R.layout.notification_app_dialog,
- null, false);
- p.mPositiveButtonText = getString(R.string.app_notifications_dialog_done);
- bindDialog(p.mView, row);
- setupAlert();
- return true;
- } else {
- Log.w(TAG, "Failed to find package info");
- }
- } else {
- Log.w(TAG, "Missing extras: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg + ", "
- + Settings.EXTRA_APP_UID + " was " + uid);
- }
- } else {
- Log.w(TAG, "No intent");
- }
- return false;
- }
-
- private static PackageInfo findPackageInfo(PackageManager pm, String pkg, int uid) {
- final String[] packages = pm.getPackagesForUid(uid);
- if (packages != null && pkg != null) {
- final int N = packages.length;
- for (int i = 0; i < N; i++) {
- final String p = packages[i];
- if (pkg.equals(p)) {
- try {
- return pm.getPackageInfo(pkg, 0);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to load package " + pkg, e);
- }
- }
- }
- }
- return null;
- }
-
- private void bindDialog(final View v, final AppRow row) {
- final ImageView icon = (ImageView) v.findViewById(android.R.id.icon);
- icon.setImageDrawable(row.icon);
- final TextView title = (TextView) v.findViewById(android.R.id.title);
- title.setText(row.label);
- final CheckBox showNotifications = (CheckBox) v.findViewById(android.R.id.button1);
- final CheckBox highPriority = (CheckBox) v.findViewById(android.R.id.button2);
- final CheckBox sensitive = (CheckBox) v.findViewById(android.R.id.button3);
-
- if (!ENABLE_APP_NOTIFICATION_PRIVACY_OPTION) {
- sensitive.setVisibility(View.GONE);
- }
-
- showNotifications.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;
- highPriority.setEnabled(!row.banned);
- sensitive.setEnabled(!row.banned);
- } else {
- showNotifications.setOnCheckedChangeListener(null);
- showNotifications.setChecked(!isChecked);
- showNotifications.setOnCheckedChangeListener(this);
- }
- }
- };
- showNotifications.setOnCheckedChangeListener(showListener);
-
- highPriority.setChecked(row.priority);
- final OnCheckedChangeListener priListener = new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- boolean success = mBackend.setHighPriority(row.pkg, row.uid, isChecked);
- if (success) {
- row.priority = isChecked;
- } else {
- highPriority.setOnCheckedChangeListener(null);
- highPriority.setChecked(!isChecked);
- highPriority.setOnCheckedChangeListener(this);
- }
- }
- };
- highPriority.setOnCheckedChangeListener(priListener);
-
- sensitive.setChecked(row.sensitive);
- final OnCheckedChangeListener senListener = new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- boolean success = mBackend.setSensitive(row.pkg, row.uid, isChecked);
- if (success) {
- row.sensitive = isChecked;
- } else {
- sensitive.setOnCheckedChangeListener(null);
- sensitive.setChecked(!isChecked);
- sensitive.setOnCheckedChangeListener(this);
- }
- }
- };
- sensitive.setOnCheckedChangeListener(senListener);
-
- highPriority.setEnabled(!row.banned);
- sensitive.setEnabled(!row.banned);
- }
-}
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
index d115ad2..6075748 100644
--- a/src/com/android/settings/notification/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -16,557 +16,186 @@
package com.android.settings.notification;
-import android.animation.LayoutTransition;
-import android.app.INotificationManager;
-import android.app.Notification;
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.drawable.Drawable;
-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.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.SwitchPreference;
import android.provider.Settings;
-import android.os.UserHandle;
-import android.os.UserManager;
+import android.text.TextUtils;
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.AdapterView.OnItemSelectedListener;
-import android.widget.AdapterView;
import android.widget.ImageView;
-import android.widget.SectionIndexer;
-import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
-import com.android.settings.Settings.AppNotificationSettingsActivity;
-import com.android.settings.PinnedHeaderListFragment;
import com.android.settings.R;
-import com.android.settings.UserSpinnerAdapter;
-import com.android.settings.Utils;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.notification.NotificationAppList.AppRow;
+import com.android.settings.notification.NotificationAppList.Backend;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/** Just a sectioned list of installed applications, nothing else to index **/
-public class AppNotificationSettings extends PinnedHeaderListFragment
- implements OnItemSelectedListener {
+/** These settings are per app, so should not be returned in global search results. */
+public class AppNotificationSettings extends SettingsPreferenceFragment {
private static final String TAG = "AppNotificationSettings";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- 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(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES);
+ private static final String KEY_BLOCK = "block";
+ private static final String KEY_PRIORITY = "priority";
+ private static final String KEY_SENSITIVE = "sensitive";
- private final Handler mHandler = new Handler();
- private final ArrayMap<String, AppRow> mRows = new ArrayMap<String, AppRow>();
- private final ArrayList<AppRow> mSortedRows = new ArrayList<AppRow>();
- private final ArrayList<String> mSections = new ArrayList<String>();
+ static final String EXTRA_HAS_SETTINGS_INTENT = "has_settings_intent";
+ static final String EXTRA_SETTINGS_INTENT = "settings_intent";
- private Context mContext;
- private LayoutInflater mInflater;
- private NotificationAppAdapter mAdapter;
- private Signature[] mSystemSignature;
- private Parcelable mListViewState;
- private Backend mBackend = new Backend();
- private UserSpinnerAdapter mProfileSpinnerAdapter;
+ private final 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);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.notification_app_list, container, false);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
- mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(um, mContext);
- if (mProfileSpinnerAdapter != null) {
- Spinner spinner = (Spinner) getActivity().getLayoutInflater().inflate(
- R.layout.spinner_view, null);
- spinner.setAdapter(mProfileSpinnerAdapter);
- spinner.setOnItemSelectedListener(this);
- setPinnedHeaderView(spinner);
- }
- }
+ private Context mContext;
+ private SwitchPreference mBlock;
+ private SwitchPreference mPriority;
+ private SwitchPreference mSensitive;
+ private AppRow mAppRow;
+ private boolean mCreated;
@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 onDestroyView() {
- super.onDestroyView();
- mListViewState = null; // you're dead to me
- }
-
- @Override
- public void onResume() {
- super.onResume();
- loadAppsList();
- }
-
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
- if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
- Intent intent = new Intent(getActivity(), AppNotificationSettingsActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, selectedUser);
- getActivity().finish();
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
-
- 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 static class ViewHolder {
- ViewGroup row;
- ViewGroup appButton;
- ImageView icon;
- TextView title;
- TextView subtitle;
- View settingsDivider;
- ImageView settingsButton;
- View rowDivider;
- }
-
- private class NotificationAppAdapter extends ArrayAdapter<Row> implements SectionIndexer {
- public NotificationAppAdapter(Context context) {
- super(context, 0, 0);
- }
-
- @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.title = (TextView) v.findViewById(android.R.id.title);
- vh.subtitle = (TextView) v.findViewById(android.R.id.text1);
- 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)) {
- // it's a section row
- final TextView tv = (TextView)view.findViewById(android.R.id.title);
- 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() {
+ if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated);
+ if (mCreated) {
+ Log.w(TAG, "onActivityCreated: ignoring duplicate call");
+ return;
+ }
+ mCreated = true;
+ if (mAppRow == null) return;
+ final View content = getActivity().findViewById(R.id.main_content);
+ final ViewGroup contentParent = (ViewGroup) content.getParent();
+ final View bar = getActivity().getLayoutInflater().inflate(R.layout.app_notification_header,
+ contentParent, false);
+
+ final ImageView appIcon = (ImageView) bar.findViewById(R.id.app_icon);
+ appIcon.setImageDrawable(mAppRow.icon);
+
+ final TextView appName = (TextView) bar.findViewById(R.id.app_name);
+ appName.setText(mAppRow.label);
+
+ final View appSettings = bar.findViewById(R.id.app_settings);
+ if (mAppRow.settingsIntent == null) {
+ appSettings.setVisibility(View.GONE);
+ } else {
+ appSettings.setClickable(true);
+ appSettings.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mContext.startActivity(new Intent(mContext, AppNotificationDialog.class)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- .putExtra(Settings.EXTRA_APP_PACKAGE, row.pkg)
- .putExtra(Settings.EXTRA_APP_UID, row.uid));
- }
- });
- enableLayoutTransitions(vh.appButton, animate);
- vh.icon.setImageDrawable(row.icon);
- vh.title.setText(row.label);
- final String sub = getSubtitle(row);
- vh.subtitle.setText(sub);
- vh.subtitle.setVisibility(!sub.isEmpty() ? View.VISIBLE : View.GONE);
- 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);
- }
+ mContext.startActivity(mAppRow.settingsIntent);
}
});
}
+ contentParent.addView(bar, 0);
+ }
- private String getSubtitle(AppRow row) {
- if (row.banned) return mContext.getString(R.string.app_notification_row_banned);
- if (!row.priority && !row.sensitive) return "";
- final String priString = mContext.getString(R.string.app_notification_row_priority);
- final String senString = mContext.getString(R.string.app_notification_row_sensitive);
- if (row.priority != row.sensitive) {
- return row.priority ? priString : senString;
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContext = getActivity();
+ Intent intent = getActivity().getIntent();
+ if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent);
+ if (intent == null) {
+ Log.w(TAG, "No intent");
+ toastAndFinish();
+ return;
+ }
+
+ final int uid = intent.getIntExtra(Settings.EXTRA_APP_UID, -1);
+ final String pkg = intent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
+ if (uid == -1 || TextUtils.isEmpty(pkg)) {
+ Log.w(TAG, "Missing extras: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg + ", "
+ + Settings.EXTRA_APP_UID + " was " + uid);
+ toastAndFinish();
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Load details for pkg=" + pkg + " uid=" + uid);
+ final PackageManager pm = getPackageManager();
+ final PackageInfo info = findPackageInfo(pm, pkg, uid);
+ if (info == null) {
+ Log.w(TAG, "Failed to find package info: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg
+ + ", " + Settings.EXTRA_APP_UID + " was " + uid);
+ toastAndFinish();
+ return;
+ }
+
+ addPreferencesFromResource(R.xml.app_notification_settings);
+ mBlock = (SwitchPreference) findPreference(KEY_BLOCK);
+ mPriority = (SwitchPreference) findPreference(KEY_PRIORITY);
+ mSensitive = (SwitchPreference) findPreference(KEY_SENSITIVE);
+
+ mAppRow = NotificationAppList.loadAppRow(pm, info, mBackend);
+ if (intent.hasExtra(EXTRA_HAS_SETTINGS_INTENT)) {
+ // use settings intent from extra
+ if (intent.getBooleanExtra(EXTRA_HAS_SETTINGS_INTENT, false)) {
+ mAppRow.settingsIntent = intent.getParcelableExtra(EXTRA_SETTINGS_INTENT);
}
- return priString + mContext.getString(R.string.summary_divider_text) + senString;
- }
-
- @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;
- }
+ } else {
+ // load settings intent
+ ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>();
+ rows.put(mAppRow.pkg, mAppRow);
+ NotificationAppList.collectConfigActivities(getPackageManager(), rows);
+ }
+
+ mBlock.setChecked(mAppRow.banned);
+ mPriority.setChecked(mAppRow.priority);
+ mSensitive.setChecked(mAppRow.sensitive);
+
+ mBlock.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean block = (Boolean) newValue;
+ return mBackend.setNotificationsBanned(pkg, uid, block);
}
- return 0;
- }
-
- @Override
- public int getSectionForPosition(int position) {
- Row row = getItem(position);
- return mSections.indexOf(row.section);
- }
- }
+ });
- private static class Row {
- public String section;
- }
+ mPriority.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean priority = (Boolean) newValue;
+ return mBackend.setHighPriority(pkg, uid, priority);
+ }
+ });
- public 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 sensitive;
- public boolean first; // first app in section
+ mSensitive.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean sensitive = (Boolean) newValue;
+ return mBackend.setSensitive(pkg, uid, sensitive);
+ }
+ });
}
- private static final Comparator<AppRow> mRowComparator = new Comparator<AppRow>() {
- private final Collator sCollator = Collator.getInstance();
- @Override
- public int compare(AppRow lhs, AppRow rhs) {
- return sCollator.compare(lhs.label, rhs.label);
- }
- };
-
- public static AppRow loadAppRow(PackageManager pm, PackageInfo pkg, Backend backend) {
- 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 = backend.getNotificationsBanned(row.pkg, row.uid);
- row.priority = backend.getHighPriority(row.pkg, row.uid);
- row.sensitive = backend.getSensitive(row.pkg, row.uid);
- return row;
+ private void toastAndFinish() {
+ Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show();
+ getActivity().finish();
}
- 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 = loadAppRow(pm, pkg, mBackend);
- mRows.put(row.pkg, row);
- }
- // collect config activities
- if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is "
- + APP_NOTIFICATION_PREFS_CATEGORY_INTENT);
- final List<ResolveInfo> 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;
+ private static PackageInfo findPackageInfo(PackageManager pm, String pkg, int uid) {
+ final String[] packages = pm.getPackagesForUid(uid);
+ if (packages != null && pkg != null) {
+ final int N = packages.length;
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;
+ final String p = packages[i];
+ if (pkg.equals(p)) {
+ try {
+ return pm.getPackageInfo(pkg, 0);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to load package " + pkg, e);
+ }
}
- 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) {
- INotificationManager nm = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- try {
- return nm.getPackagePriority(pkg, uid) == Notification.PRIORITY_MAX;
- } catch (Exception e) {
- Log.w(TAG, "Error calling NoMan", e);
- return false;
- }
- }
-
- public boolean setHighPriority(String pkg, int uid, boolean highPriority) {
- INotificationManager nm = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- try {
- nm.setPackagePriority(pkg, uid,
- highPriority ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT);
- return true;
- } catch (Exception e) {
- Log.w(TAG, "Error calling NoMan", e);
- return false;
- }
- }
-
- public boolean getSensitive(String pkg, int uid) {
- // TODO get visibility state from NoMan
- return false;
- }
-
- public boolean setSensitive(String pkg, int uid, boolean sensitive) {
- // TODO save visibility state to NoMan
- return true;
- }
+ return null;
}
}
diff --git a/src/com/android/settings/notification/NotificationAppList.java b/src/com/android/settings/notification/NotificationAppList.java
new file mode 100644
index 0000000..3879bef
--- /dev/null
+++ b/src/com/android/settings/notification/NotificationAppList.java
@@ -0,0 +1,569 @@
+/*
+ * 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 static com.android.settings.notification.AppNotificationSettings.EXTRA_HAS_SETTINGS_INTENT;
+import static com.android.settings.notification.AppNotificationSettings.EXTRA_SETTINGS_INTENT;
+
+import android.animation.LayoutTransition;
+import android.app.INotificationManager;
+import android.app.Notification;
+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.drawable.Drawable;
+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.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+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.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.SectionIndexer;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.settings.PinnedHeaderListFragment;
+import com.android.settings.R;
+import com.android.settings.Settings.NotificationAppListActivity;
+import com.android.settings.UserSpinnerAdapter;
+import com.android.settings.Utils;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/** Just a sectioned list of installed applications, nothing else to index **/
+public class NotificationAppList extends PinnedHeaderListFragment
+ implements OnItemSelectedListener {
+ private static final String TAG = "NotificationAppList";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String EMPTY_SUBTITLE = "";
+ 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(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES);
+
+ private final Handler mHandler = new Handler();
+ private final ArrayMap<String, AppRow> mRows = new ArrayMap<String, AppRow>();
+ private final ArrayList<AppRow> mSortedRows = new ArrayList<AppRow>();
+ private final ArrayList<String> mSections = new ArrayList<String>();
+
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private NotificationAppAdapter mAdapter;
+ private Signature[] mSystemSignature;
+ private Parcelable mListViewState;
+ private Backend mBackend = new Backend();
+ private UserSpinnerAdapter mProfileSpinnerAdapter;
+
+ @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);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.notification_app_list, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
+ mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(um, mContext);
+ if (mProfileSpinnerAdapter != null) {
+ Spinner spinner = (Spinner) getActivity().getLayoutInflater().inflate(
+ R.layout.spinner_view, null);
+ spinner.setAdapter(mProfileSpinnerAdapter);
+ spinner.setOnItemSelectedListener(this);
+ setPinnedHeaderView(spinner);
+ }
+ }
+
+ @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 onDestroyView() {
+ super.onDestroyView();
+ mListViewState = null; // you're dead to me
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ loadAppsList();
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
+ if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
+ Intent intent = new Intent(getActivity(), NotificationAppListActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(intent, selectedUser);
+ getActivity().finish();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+ 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 static class ViewHolder {
+ ViewGroup row;
+ ImageView icon;
+ TextView title;
+ TextView subtitle;
+ View rowDivider;
+ }
+
+ private class NotificationAppAdapter extends ArrayAdapter<Row> implements SectionIndexer {
+ public NotificationAppAdapter(Context context) {
+ super(context, 0, 0);
+ }
+
+ @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.row.setLayoutTransition(new LayoutTransition());
+ vh.icon = (ImageView) v.findViewById(android.R.id.icon);
+ vh.title = (TextView) v.findViewById(android.R.id.title);
+ vh.subtitle = (TextView) v.findViewById(android.R.id.text1);
+ 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)) {
+ // it's a section row
+ final TextView tv = (TextView)view.findViewById(android.R.id.title);
+ 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.row.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, row.pkg)
+ .putExtra(Settings.EXTRA_APP_UID, row.uid)
+ .putExtra(EXTRA_HAS_SETTINGS_INTENT, row.settingsIntent != null)
+ .putExtra(EXTRA_SETTINGS_INTENT, row.settingsIntent));
+ }
+ });
+ enableLayoutTransitions(vh.row, animate);
+ vh.icon.setImageDrawable(row.icon);
+ vh.title.setText(row.label);
+ final String sub = getSubtitle(row);
+ vh.subtitle.setText(sub);
+ vh.subtitle.setVisibility(!sub.isEmpty() ? View.VISIBLE : View.GONE);
+ }
+
+ private String getSubtitle(AppRow row) {
+ if (row.banned) {
+ return mContext.getString(R.string.app_notification_row_banned);
+ }
+ if (!row.priority && !row.sensitive) {
+ return EMPTY_SUBTITLE;
+ }
+ final String priString = mContext.getString(R.string.app_notification_row_priority);
+ final String senString = mContext.getString(R.string.app_notification_row_sensitive);
+ if (row.priority != row.sensitive) {
+ return row.priority ? priString : senString;
+ }
+ return priString + mContext.getString(R.string.summary_divider_text) + senString;
+ }
+
+ @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;
+ }
+
+ public 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 sensitive;
+ public boolean first; // first app in section
+ }
+
+ private static final Comparator<AppRow> mRowComparator = new Comparator<AppRow>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppRow lhs, AppRow rhs) {
+ return sCollator.compare(lhs.label, rhs.label);
+ }
+ };
+
+ public static AppRow loadAppRow(PackageManager pm, PackageInfo pkg, Backend backend) {
+ 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 = backend.getNotificationsBanned(row.pkg, row.uid);
+ row.priority = backend.getHighPriority(row.pkg, row.uid);
+ row.sensitive = backend.getSensitive(row.pkg, row.uid);
+ return row;
+ }
+
+ public static void collectConfigActivities(PackageManager pm, ArrayMap<String, AppRow> rows) {
+ if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is "
+ + APP_NOTIFICATION_PREFS_CATEGORY_INTENT);
+ final List<ResolveInfo> 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 = rows.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);
+ }
+ }
+
+ 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 = loadAppRow(pm, pkg, mBackend);
+ mRows.put(row.pkg, row);
+ }
+ // collect config activities
+ collectConfigActivities(pm, mRows);
+ // 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) {
+ INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ return nm.getPackagePriority(pkg, uid) == Notification.PRIORITY_MAX;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean setHighPriority(String pkg, int uid, boolean highPriority) {
+ INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ nm.setPackagePriority(pkg, uid,
+ highPriority ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean getSensitive(String pkg, int uid) {
+ // TODO get visibility state from NoMan
+ return false;
+ }
+
+ public boolean setSensitive(String pkg, int uid, boolean sensitive) {
+ // TODO save visibility state to NoMan
+ return true;
+ }
+ }
+}