diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 883ded3d915..125e90edc4b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2084,7 +2084,7 @@ android:exported="true" android:taskAffinity=""> + android:value="com.android.settings.applications.ManageApplications" /> diff --git a/res/layout/notification_app_list.xml b/res/layout/notification_app_list.xml deleted file mode 100644 index 2eac287ce6f..00000000000 --- a/res/layout/notification_app_list.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index be27f83a05d..ec93db3bbc5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6103,6 +6103,12 @@ Personal Work + + Blocked + + Priority + + Sensitive Reset preferences across all apps to defaults diff --git a/res/xml/notification_settings.xml b/res/xml/notification_settings.xml index ac61b001b3e..f35d248c636 100644 --- a/res/xml/notification_settings.xml +++ b/res/xml/notification_settings.xml @@ -114,7 +114,11 @@ + android:fragment="com.android.settings.applications.ManageApplications"> + + apps) { + // No op. + } + + @Override + public void onPackageIconChanged() { + // No op. + } + + @Override + public void onPackageSizeChanged(String packageName) { + // No op. + } + + @Override + public void onAllSizesComputed() { + // No op. + } + + @Override + public void onLauncherInfoChanged() { + // No op. + } + + private class MainHandler extends Handler { + private static final int MSG_NOTIF_UPDATED = 1; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_NOTIF_UPDATED: + mCallback.onNotificationInfoUpdated(); + break; + } + } + } + + private class BackgroundHandler extends Handler { + private static final int MSG_LOAD_ALL = 1; + private static final int MSG_FORCE_LOAD_PKG = 2; + + public BackgroundHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + List apps = mAppSession.getAllApps(); + final int N = apps.size(); + switch (msg.what) { + case MSG_LOAD_ALL: + for (int i = 0; i < N; i++) { + AppEntry app = apps.get(i); + app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info); + } + mMainHandler.sendEmptyMessage(MainHandler.MSG_NOTIF_UPDATED); + break; + case MSG_FORCE_LOAD_PKG: + String pkg = (String) msg.obj; + int uid = msg.arg1; + for (int i = 0; i < N; i++) { + AppEntry app = apps.get(i); + if (app.info.uid == uid && pkg.equals(app.info.packageName)) { + app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info); + break; + } + } + mMainHandler.sendEmptyMessage(MainHandler.MSG_NOTIF_UPDATED); + break; + } + } + } + + public interface Callback { + void onNotificationInfoUpdated(); + } + + public static final AppFilter FILTER_APP_NOTIFICATION_BLOCKED = new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(AppEntry info) { + return info.extraInfo != null && ((AppRow) info.extraInfo).banned; + } + }; + + public static final AppFilter FILTER_APP_NOTIFICATION_PRIORITY = new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(AppEntry info) { + return info.extraInfo != null && ((AppRow) info.extraInfo).priority; + } + }; + + public static final AppFilter FILTER_APP_NOTIFICATION_SENSITIVE = new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(AppEntry info) { + return info.extraInfo != null && ((AppRow) info.extraInfo).sensitive; + } + }; +} diff --git a/src/com/android/settings/applications/AppViewHolder.java b/src/com/android/settings/applications/AppViewHolder.java index c4324d1bc2f..176ccca729f 100644 --- a/src/com/android/settings/applications/AppViewHolder.java +++ b/src/com/android/settings/applications/AppViewHolder.java @@ -15,7 +15,7 @@ public class AppViewHolder { public View rootView; public TextView appName; public ImageView appIcon; - public TextView appSize; + public TextView summary; public TextView disabled; public CheckBox checkBox; @@ -29,7 +29,7 @@ public class AppViewHolder { holder.rootView = convertView; holder.appName = (TextView) convertView.findViewById(R.id.app_name); holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); - holder.appSize = (TextView) convertView.findViewById(R.id.app_size); + holder.summary = (TextView) convertView.findViewById(R.id.app_size); holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard); convertView.setTag(holder); @@ -42,22 +42,22 @@ public class AppViewHolder { } void updateSizeText(CharSequence invalidSizeStr, int whichSize) { - if (ManageApplications.DEBUG) Log.i(ManageApplications.TAG, "updateSizeText of " + entry.label + " " + entry - + ": " + entry.sizeStr); + if (ManageApplications.DEBUG) Log.i(ManageApplications.TAG, "updateSizeText of " + + entry.label + " " + entry + ": " + entry.sizeStr); if (entry.sizeStr != null) { switch (whichSize) { case ManageApplications.SIZE_INTERNAL: - appSize.setText(entry.internalSizeStr); + summary.setText(entry.internalSizeStr); break; case ManageApplications.SIZE_EXTERNAL: - appSize.setText(entry.externalSizeStr); + summary.setText(entry.externalSizeStr); break; default: - appSize.setText(entry.sizeStr); + summary.setText(entry.sizeStr); break; } } else if (entry.size == ApplicationsState.SIZE_INVALID) { - appSize.setText(invalidSizeStr); + summary.setText(invalidSizeStr); } } } \ No newline at end of file diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java index 51a15cf8145..1aca69adc98 100644 --- a/src/com/android/settings/applications/ApplicationsState.java +++ b/src/com/android/settings/applications/ApplicationsState.java @@ -57,6 +57,7 @@ public class ApplicationsState { public void onPackageSizeChanged(String packageName); public void onAllSizesComputed(); public void onLauncherInfoChanged(); + public void onLoadEntriesCompleted(); } public static interface AppFilter { @@ -125,6 +126,9 @@ public class ApplicationsState { String normalizedLabel; + // A location where extra info can be placed to be used by custom filters. + Object extraInfo; + AppEntry(Context context, ApplicationInfo info, long id) { apkFile = new File(info.sourceDir); this.id = id; @@ -444,6 +448,7 @@ public class ApplicationsState { static final int MSG_ALL_SIZES_COMPUTED = 5; static final int MSG_RUNNING_STATE_CHANGED = 6; static final int MSG_LAUNCHER_INFO_CHANGED = 7; + static final int MSG_LOAD_ENTRIES_COMPLETE = 8; @Override public void handleMessage(Message msg) { @@ -487,6 +492,11 @@ public class ApplicationsState { mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged(); } } break; + case MSG_LOAD_ENTRIES_COMPLETE: { + for (int i=0; i= 6) { sendEmptyMessage(MSG_LOAD_ENTRIES); } else { + if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE); + } sendEmptyMessage(MSG_LOAD_LAUNCHER); } } break; diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 85b35235c0f..93168103d90 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -62,9 +62,8 @@ import com.android.settings.Utils; import com.android.settings.applications.ApplicationsState.AppEntry; import com.android.settings.net.ChartData; import com.android.settings.net.ChartDataLoader; -import com.android.settings.notification.NotificationAppList; -import com.android.settings.notification.NotificationAppList.AppRow; -import com.android.settings.notification.NotificationAppList.Backend; +import com.android.settings.notification.NotificationBackend; +import com.android.settings.notification.NotificationBackend.AppRow; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -122,7 +121,7 @@ public class InstalledAppDetails extends AppInfoBase private boolean mDisableAfterUninstall; // Used for updating notification preference. - private final Backend mBackend = new Backend(); + private final NotificationBackend mBackend = new NotificationBackend(); private ChartData mChartData; private INetworkStatsSession mStatsSession; @@ -636,13 +635,16 @@ public class InstalledAppDetails extends AppInfoBase } public static CharSequence getNotificationSummary(AppEntry appEntry, Context context) { - return getNotificationSummary(appEntry, context, new Backend()); + return getNotificationSummary(appEntry, context, new NotificationBackend()); } public static CharSequence getNotificationSummary(AppEntry appEntry, Context context, - Backend backend) { - AppRow appRow = NotificationAppList.loadAppRow(context.getPackageManager(), appEntry.info, - backend); + NotificationBackend backend) { + AppRow appRow = backend.loadAppRow(context.getPackageManager(), appEntry.info); + return getNotificationSummary(appRow, context); + } + + public static CharSequence getNotificationSummary(AppRow appRow, Context context) { if (appRow.banned) { return context.getString(R.string.notifications_disabled); } else if (appRow.priority) { diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java index 14d48c8faf9..a034964494d 100644 --- a/src/com/android/settings/applications/ManageApplications.java +++ b/src/com/android/settings/applications/ManageApplications.java @@ -54,10 +54,13 @@ import android.widget.Spinner; import com.android.internal.content.PackageHelper; import com.android.settings.R; import com.android.settings.Settings.AllApplicationsActivity; +import com.android.settings.Settings.NotificationAppListActivity; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.applications.ApplicationsState.AppEntry; import com.android.settings.applications.ApplicationsState.AppFilter; +import com.android.settings.notification.NotificationBackend; +import com.android.settings.notification.NotificationBackend.AppRow; import java.util.ArrayList; import java.util.Collections; @@ -135,8 +138,11 @@ public class ManageApplications extends Fragment implements OnItemClickListener, public static final int FILTER_APPS_ALL = 1; public static final int FILTER_APPS_ENABLED = 2; public static final int FILTER_APPS_DISABLED = 3; - public static final int FILTER_APPS_PERSONAL = 4; - public static final int FILTER_APPS_WORK = 5; + public static final int FILTER_APPS_BLOCKED = 4; + public static final int FILTER_APPS_PRIORITY = 5; + public static final int FILTER_APPS_SENSITIVE = 6; + public static final int FILTER_APPS_PERSONAL = 7; + public static final int FILTER_APPS_WORK = 8; // This is the string labels for the filter modes above, the order must be kept in sync. public static final int[] FILTER_LABELS = new int[] { @@ -144,6 +150,9 @@ public class ManageApplications extends Fragment implements OnItemClickListener, R.string.filter_all_apps, // All apps R.string.filter_enabled_apps, // Enabled R.string.filter_apps_disabled, // Disabled + R.string.filter_notif_blocked_apps, // Blocked Notifications + R.string.filter_notif_priority_apps, // Priority Notifications + R.string.filter_notif_sensitive_apps, // Sensitive Notifications R.string.filter_personal_apps, // Personal R.string.filter_work_apps, // Work }; @@ -154,6 +163,9 @@ public class ManageApplications extends Fragment implements OnItemClickListener, ApplicationsState.FILTER_EVERYTHING, // All apps ApplicationsState.FILTER_ALL_ENABLED, // Enabled ApplicationsState.FILTER_DISABLED, // Disabled + AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED, // Blocked Notifications + AppStateNotificationBridge.FILTER_APP_NOTIFICATION_PRIORITY, // Priority Notifications + AppStateNotificationBridge.FILTER_APP_NOTIFICATION_SENSITIVE, // Sensitive Notifications ApplicationsState.FILTER_PERSONAL, // Personal ApplicationsState.FILTER_WORK, // Work }; @@ -194,12 +206,14 @@ public class ManageApplications extends Fragment implements OnItemClickListener, public static final int LIST_TYPE_MAIN = 0; public static final int LIST_TYPE_ALL = 1; + public static final int LIST_TYPE_NOTIFICATION = 2; private View mRootView; private View mSpinnerHeader; private Spinner mFilterSpinner; private FilterSpinnerAdapter mFilterAdapter; + private NotificationBackend mNotifBackend; @Override public void onCreate(Bundle savedInstanceState) { @@ -214,9 +228,11 @@ public class ManageApplications extends Fragment implements OnItemClickListener, if (className == null) { className = intent.getComponent().getClassName(); } - if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action) - || className.equals(AllApplicationsActivity.class.getName())) { + if (className.equals(AllApplicationsActivity.class.getName())) { mListType = LIST_TYPE_ALL; + } else if (className.equals(NotificationAppListActivity.class.getName())) { + mListType = LIST_TYPE_NOTIFICATION; + mNotifBackend = new NotificationBackend(); } else { mListType = LIST_TYPE_MAIN; } @@ -289,6 +305,11 @@ public class ManageApplications extends Fragment implements OnItemClickListener, mFilterAdapter.enableFilter(FILTER_APPS_WORK); } } + if (mListType == LIST_TYPE_NOTIFICATION) { + mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED); + mFilterAdapter.enableFilter(FILTER_APPS_PRIORITY); + mFilterAdapter.enableFilter(FILTER_APPS_SENSITIVE); + } } private int getDefaultFilter() { @@ -335,19 +356,30 @@ public class ManageApplications extends Fragment implements OnItemClickListener, @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { - mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid)); + if (mListType == LIST_TYPE_NOTIFICATION) { + mApplications.mNotifBridge.forceUpdate(mCurrentPkgName, mCurrentUid); + } else { + mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid)); + } } } // utility method used to start sub activity private void startApplicationDetailsActivity() { - // TODO: Figure out if there is a way where we can spin up the profile's settings - // process ahead of time, to avoid a long load of data when user clicks on a managed app. - // Maybe when they load the list of apps that contains managed profile apps. - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", mCurrentPkgName, null)); - getActivity().startActivityAsUser(intent, - new UserHandle(UserHandle.getUserId(mCurrentUid))); + if (mListType == LIST_TYPE_NOTIFICATION) { + getActivity().startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(Settings.EXTRA_APP_PACKAGE, mCurrentPkgName) + .putExtra(Settings.EXTRA_APP_UID, mCurrentUid)); + } else { + // TODO: Figure out if there is a way where we can spin up the profile's settings + // process ahead of time, to avoid a long load of data when user clicks on a managed app. + // Maybe when they load the list of apps that contains managed profile apps. + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", mCurrentPkgName, null)); + getActivity().startActivityAsUser(intent, + new UserHandle(UserHandle.getUserId(mCurrentUid))); + } } @Override @@ -517,12 +549,14 @@ public class ManageApplications extends Fragment implements OnItemClickListener, * The order of applications in the list is mirrored in mAppLocalList */ static class ApplicationsAdapter extends BaseAdapter implements Filterable, - ApplicationsState.Callbacks, AbsListView.RecyclerListener { + ApplicationsState.Callbacks, AppStateNotificationBridge.Callback, + AbsListView.RecyclerListener { private final ApplicationsState mState; private final ApplicationsState.Session mSession; private final ManageApplications mManageApplications; private final Context mContext; private final ArrayList mActive = new ArrayList(); + private final AppStateNotificationBridge mNotifBridge; private int mFilterMode; private ArrayList mBaseEntries; private ArrayList mEntries; @@ -558,6 +592,13 @@ public class ManageApplications extends Fragment implements OnItemClickListener, mManageApplications = manageApplications; mContext = manageApplications.getActivity(); mFilterMode = filterMode; + if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) { + mNotifBridge = new AppStateNotificationBridge( + mContext.getPackageManager(), mState, + manageApplications.mNotifBackend, this); + } else { + mNotifBridge = null; + } } public void setFilter(int filter) { @@ -571,6 +612,9 @@ public class ManageApplications extends Fragment implements OnItemClickListener, mResumed = true; mSession.resume(); mLastSortMode = sort; + if (mNotifBridge != null) { + mNotifBridge.resume(); + } rebuild(true); } else { rebuild(sort); @@ -581,11 +625,17 @@ public class ManageApplications extends Fragment implements OnItemClickListener, if (mResumed) { mResumed = false; mSession.pause(); + if (mNotifBridge != null) { + mNotifBridge.pause(); + } } } public void release() { mSession.release(); + if (mNotifBridge != null) { + mNotifBridge.release(); + } } public void rebuild(int sort) { @@ -680,6 +730,13 @@ public class ManageApplications extends Fragment implements OnItemClickListener, } } + @Override + public void onNotificationInfoUpdated() { + if (mFilterMode != mManageApplications.getDefaultFilter()) { + rebuild(false); + } + } + @Override public void onRunningStateChanged(boolean running) { mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running); @@ -711,13 +768,20 @@ public class ManageApplications extends Fragment implements OnItemClickListener, // don't care about icons loaded in the background. } + @Override + public void onLoadEntriesCompleted() { + // No op. + } + @Override public void onPackageSizeChanged(String packageName) { for (int i=0; i rows = new ArrayMap(); rows.put(mAppRow.pkg, mAppRow); - NotificationAppList.collectConfigActivities(getPackageManager(), rows); + collectConfigActivities(getPackageManager(), rows); } mBlock.setChecked(mAppRow.banned); @@ -225,4 +235,44 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { } return null; } + + public static List queryNotificationConfigActivities(PackageManager pm) { + if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is " + + APP_NOTIFICATION_PREFS_CATEGORY_INTENT); + final List resolveInfos = pm.queryIntentActivities( + APP_NOTIFICATION_PREFS_CATEGORY_INTENT, + 0 //PackageManager.MATCH_DEFAULT_ONLY + ); + return resolveInfos; + } + + public static void collectConfigActivities(PackageManager pm, ArrayMap rows) { + final List resolveInfos = queryNotificationConfigActivities(pm); + applyConfigActivities(pm, rows, resolveInfos); + } + + public static void applyConfigActivities(PackageManager pm, ArrayMap rows, + List resolveInfos) { + if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" + + (resolveInfos.size() == 0 ? " ;_;" : "")); + for (ResolveInfo ri : resolveInfos) { + final ActivityInfo activityInfo = ri.activityInfo; + final ApplicationInfo appInfo = activityInfo.applicationInfo; + final AppRow row = rows.get(appInfo.packageName); + if (row == null) { + if (DEBUG) Log.v(TAG, "Ignoring notification preference activity (" + + activityInfo.name + ") for unknown package " + + activityInfo.packageName); + continue; + } + if (row.settingsIntent != null) { + if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity (" + + activityInfo.name + ") for package " + + activityInfo.packageName); + continue; + } + row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT) + .setClassName(activityInfo.packageName, activityInfo.name); + } + } } diff --git a/src/com/android/settings/notification/NotificationAppList.java b/src/com/android/settings/notification/NotificationAppList.java deleted file mode 100644 index 27eb9145ec3..00000000000 --- a/src/com/android/settings/notification/NotificationAppList.java +++ /dev/null @@ -1,623 +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 static com.android.settings.notification.AppNotificationSettings.EXTRA_HAS_SETTINGS_INTENT; -import static com.android.settings.notification.AppNotificationSettings.EXTRA_SETTINGS_INTENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; - -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.LauncherActivityInfo; -import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; -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.service.notification.NotificationListenerService; -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.view.ViewGroup.LayoutParams; -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 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(); - private UserSpinnerAdapter mProfileSpinnerAdapter; - private Spinner mSpinner; - - private PackageManager mPM; - private UserManager mUM; - private LauncherApps mLauncherApps; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mContext = getActivity(); - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mAdapter = new NotificationAppAdapter(mContext); - mUM = UserManager.get(mContext); - mPM = mContext.getPackageManager(); - mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE); - 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); - mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(mUM, mContext); - if (mProfileSpinnerAdapter != null) { - mSpinner = (Spinner) getActivity().getLayoutInflater().inflate( - R.layout.spinner_view, null); - mSpinner.setAdapter(mProfileSpinnerAdapter); - mSpinner.setOnItemSelectedListener(this); - // Set layout parameters, otherwise we get the default ones - mSpinner.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - setPinnedHeaderView(mSpinner); - } - } - - @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); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivityAsUser(intent, selectedUser); - // Go back to default selection, which is the first one; this makes sure that pressing - // the back button takes you into a consistent state - mSpinner.setSelection(0); - } - } - - @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 static class ViewHolder { - ViewGroup row; - ImageView icon; - TextView title; - TextView subtitle; - View rowDivider; - } - - private class NotificationAppAdapter extends ArrayAdapter 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 peekable; - public boolean sensitive; - public boolean first; // first app in section - } - - 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); - } - }; - - - public static AppRow loadAppRow(PackageManager pm, ApplicationInfo app, - Backend backend) { - final AppRow row = new AppRow(); - row.pkg = app.packageName; - row.uid = app.uid; - try { - row.label = app.loadLabel(pm); - } catch (Throwable t) { - Log.e(TAG, "Error loading application label for " + row.pkg, t); - row.label = row.pkg; - } - row.icon = app.loadIcon(pm); - row.banned = backend.getNotificationsBanned(row.pkg, row.uid); - row.priority = backend.getHighPriority(row.pkg, row.uid); - row.peekable = backend.getPeekable(row.pkg, row.uid); - row.sensitive = backend.getSensitive(row.pkg, row.uid); - return row; - } - - public static List queryNotificationConfigActivities(PackageManager pm) { - if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is " - + APP_NOTIFICATION_PREFS_CATEGORY_INTENT); - final List resolveInfos = pm.queryIntentActivities( - APP_NOTIFICATION_PREFS_CATEGORY_INTENT, - 0 //PackageManager.MATCH_DEFAULT_ONLY - ); - return resolveInfos; - } - public static void collectConfigActivities(PackageManager pm, ArrayMap rows) { - final List resolveInfos = queryNotificationConfigActivities(pm); - applyConfigActivities(pm, rows, resolveInfos); - } - - public static void applyConfigActivities(PackageManager pm, ArrayMap rows, - List resolveInfos) { - if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" - + (resolveInfos.size() == 0 ? " ;_;" : "")); - 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(APP_NOTIFICATION_PREFS_CATEGORY_INTENT) - .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 launchable apps, plus any packages that have notification settings - final List appInfos = new ArrayList(); - - final List lais - = mLauncherApps.getActivityList(null /* all */, - UserHandle.getCallingUserHandle()); - if (DEBUG) Log.d(TAG, " launchable activities:"); - for (LauncherActivityInfo lai : lais) { - if (DEBUG) Log.d(TAG, " " + lai.getComponentName().toString()); - appInfos.add(lai.getApplicationInfo()); - } - - final List resolvedConfigActivities - = queryNotificationConfigActivities(mPM); - if (DEBUG) Log.d(TAG, " config activities:"); - for (ResolveInfo ri : resolvedConfigActivities) { - if (DEBUG) Log.d(TAG, " " - + ri.activityInfo.packageName + "/" + ri.activityInfo.name); - appInfos.add(ri.activityInfo.applicationInfo); - } - - for (ApplicationInfo info : appInfos) { - final String key = info.packageName; - if (mRows.containsKey(key)) { - // we already have this app, thanks - continue; - } - - final AppRow row = loadAppRow(mPM, info, mBackend); - mRows.put(key, row); - } - - // add config activities to the list - applyConfigActivities(mPM, mRows, resolvedConfigActivities); - - // 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 { - static INotificationManager sINM = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - - public boolean setNotificationsBanned(String pkg, int uid, boolean banned) { - try { - sINM.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) { - try { - final boolean enabled = sINM.areNotificationsEnabledForPackage(pkg, uid); - return !enabled; - } catch (Exception e) { - Log.w(TAG, "Error calling NoMan", e); - return false; - } - } - - public boolean getHighPriority(String pkg, int uid) { - try { - return sINM.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) { - try { - sINM.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 getPeekable(String pkg, int uid) { - try { - return sINM.getPackagePeekable(pkg, uid); - } catch (Exception e) { - Log.w(TAG, "Error calling NoMan", e); - return false; - } - } - - public boolean setPeekable(String pkg, int uid, boolean peekable) { - try { - sINM.setPackagePeekable(pkg, uid, peekable); - return true; - } catch (Exception e) { - Log.w(TAG, "Error calling NoMan", e); - return false; - } - } - - public boolean getSensitive(String pkg, int uid) { - try { - return sINM.getPackageVisibilityOverride(pkg, uid) == Notification.VISIBILITY_PRIVATE; - } catch (Exception e) { - Log.w(TAG, "Error calling NoMan", e); - return false; - } - } - - public boolean setSensitive(String pkg, int uid, boolean sensitive) { - try { - sINM.setPackageVisibilityOverride(pkg, uid, - sensitive ? Notification.VISIBILITY_PRIVATE - : NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); - return true; - } catch (Exception e) { - Log.w(TAG, "Error calling NoMan", e); - return false; - } - } - } -} diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java new file mode 100644 index 00000000000..2060719b530 --- /dev/null +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 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.app.INotificationManager; +import android.app.Notification; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.ServiceManager; +import android.service.notification.NotificationListenerService; +import android.util.Log; + +public class NotificationBackend { + private static final String TAG = "NotificationBackend"; + + static INotificationManager sINM = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + + public AppRow loadAppRow(PackageManager pm, ApplicationInfo app) { + final AppRow row = new AppRow(); + row.pkg = app.packageName; + row.uid = app.uid; + try { + row.label = app.loadLabel(pm); + } catch (Throwable t) { + Log.e(TAG, "Error loading application label for " + row.pkg, t); + row.label = row.pkg; + } + row.icon = app.loadIcon(pm); + row.banned = getNotificationsBanned(row.pkg, row.uid); + row.priority = getHighPriority(row.pkg, row.uid); + row.peekable = getPeekable(row.pkg, row.uid); + row.sensitive = getSensitive(row.pkg, row.uid); + return row; + } + + public boolean setNotificationsBanned(String pkg, int uid, boolean banned) { + try { + sINM.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) { + try { + final boolean enabled = sINM.areNotificationsEnabledForPackage(pkg, uid); + return !enabled; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean getHighPriority(String pkg, int uid) { + try { + return sINM.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) { + try { + sINM.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 getPeekable(String pkg, int uid) { + try { + return sINM.getPackagePeekable(pkg, uid); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean setPeekable(String pkg, int uid, boolean peekable) { + try { + sINM.setPackagePeekable(pkg, uid, peekable); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean getSensitive(String pkg, int uid) { + try { + return sINM.getPackageVisibilityOverride(pkg, uid) == Notification.VISIBILITY_PRIVATE; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean setSensitive(String pkg, int uid, boolean sensitive) { + try { + sINM.setPackageVisibilityOverride(pkg, uid, + sensitive ? Notification.VISIBILITY_PRIVATE + : NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + 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 peekable; + public boolean sensitive; + public boolean first; // first app in section + } + +}