Make notifications app list use new manage apps

The designs for Settings have the notification app list using the
same UI as the Manage Apps list, so switch the notification app
list over to the ManageApplications fragment.  This involves
adding some notification based filters and connecting the data
from the Notification Backend to ApplicationsState.

Bug: 19443900
Change-Id: I5e5cdb16890d536613ee59292b89a89b6fb9e2e6
This commit is contained in:
Jason Monk
2015-03-20 14:56:28 -04:00
parent becc984a47
commit 584b2b2bc1
16 changed files with 555 additions and 708 deletions

View File

@@ -254,4 +254,9 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements C
// No-op.
}
@Override
public void onLoadEntriesCompleted() {
// No-op.
}
}

View File

@@ -184,6 +184,11 @@ public abstract class AppInfoBase extends PreferenceFragment
// No op.
}
@Override
public void onLoadEntriesCompleted() {
// No op.
}
@Override
public void onPackageListChanged() {
refreshUi();

View File

@@ -0,0 +1,203 @@
/*
* 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.applications;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.android.settings.applications.ApplicationsState.AppEntry;
import com.android.settings.applications.ApplicationsState.AppFilter;
import com.android.settings.applications.ApplicationsState.Session;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.NotificationBackend.AppRow;
import java.util.ArrayList;
import java.util.List;
/**
* Connects the info provided by ApplicationsState and the NotificationBackend.
* Also provides app filters that can use the notification data.
*/
public class AppStateNotificationBridge implements ApplicationsState.Callbacks {
private final ApplicationsState mAppState;
private final NotificationBackend mNotifBackend;
private final Session mAppSession;
private final Callback mCallback;
private final BackgroundHandler mHandler;
private final MainHandler mMainHandler;
private final PackageManager mPm;
public AppStateNotificationBridge(PackageManager pm, ApplicationsState appState,
NotificationBackend notifBackend, Callback callback) {
mAppState = appState;
mPm = pm;
mAppSession = mAppState.newSession(this);
mNotifBackend = notifBackend;
mCallback = callback;
// Running on the same background thread as the ApplicationsState lets
// us run in the background and make sure they aren't doing updates at
// the same time as us as well.
mHandler = new BackgroundHandler(mAppState.getBackgroundLooper());
mMainHandler = new MainHandler();
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
}
public void resume() {
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
mAppSession.resume();
}
public void pause() {
mAppSession.pause();
}
public void release() {
mAppSession.release();
}
public void forceUpdate(String pkg, int uid) {
mHandler.obtainMessage(BackgroundHandler.MSG_FORCE_LOAD_PKG, uid, 0, pkg).sendToTarget();
}
@Override
public void onPackageListChanged() {
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
}
@Override
public void onLoadEntriesCompleted() {
mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
}
@Override
public void onRunningStateChanged(boolean running) {
// No op.
}
@Override
public void onRebuildComplete(ArrayList<AppEntry> 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<AppEntry> 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;
}
};
}

View File

@@ -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);
}
}
}

View File

@@ -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<mActiveSessions.size(); i++) {
mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
}
} break;
}
}
}
@@ -552,6 +562,10 @@ public class ApplicationsState {
}
}
Looper getBackgroundLooper() {
return mThread.getLooper();
}
public class Session {
final Callbacks mCallbacks;
boolean mResumed;
@@ -1131,6 +1145,9 @@ public class ApplicationsState {
if (numDone >= 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;

View File

@@ -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) {

View File

@@ -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<View> mActive = new ArrayList<View>();
private final AppStateNotificationBridge mNotifBridge;
private int mFilterMode;
private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
private ArrayList<ApplicationsState.AppEntry> 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<mActive.size(); i++) {
AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
if (holder.entry.info.packageName.equals(packageName)) {
synchronized (holder.entry) {
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
if (mManageApplications.mListType != LIST_TYPE_NOTIFICATION) {
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
}
}
if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
&& mLastSortMode == SORT_ORDER_SIZE) {
@@ -780,7 +844,16 @@ public class ManageApplications extends Fragment implements OnItemClickListener,
if (entry.icon != null) {
holder.appIcon.setImageDrawable(entry.icon);
}
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
if (entry.extraInfo != null) {
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
(AppRow) entry.extraInfo, mContext));
} else {
holder.summary.setText("");
}
} else {
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
}
if ((entry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
holder.disabled.setVisibility(View.VISIBLE);
holder.disabled.setText(R.string.not_installed);