New UX for app usage screen

Now uses ManageApplications base, and has a details screen which has
a switch and a link to optional app settings.

Bug: 20290386
Change-Id: If32ce8d82e55f3908644c575925b3f6506a68e6e
This commit is contained in:
Jason Monk
2015-04-17 14:34:12 -04:00
parent 6d61061518
commit d8da51ccfe
13 changed files with 728 additions and 757 deletions

View File

@@ -32,7 +32,6 @@ import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceFragment;
import android.util.Log;
import com.android.settings.InstrumentedPreferenceFragment;

View File

@@ -0,0 +1,158 @@
/*
* 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.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.android.settings.applications.ApplicationsState.AppEntry;
import com.android.settings.applications.ApplicationsState.Session;
import java.util.ArrayList;
/**
* Common base class for bridging information to ApplicationsState.
*/
public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks {
protected final ApplicationsState mAppState;
protected final Session mAppSession;
protected final Callback mCallback;
protected final BackgroundHandler mHandler;
protected final MainHandler mMainHandler;
public AppStateBaseBridge(ApplicationsState appState, Callback callback) {
mAppState = appState;
mAppSession = mAppState != null ? mAppState.newSession(this) : null;
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();
}
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.
}
protected abstract void loadAllExtraInfo();
protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid);
private class MainHandler extends Handler {
private static final int MSG_INFO_UPDATED = 1;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INFO_UPDATED:
mCallback.onExtraInfoUpdated();
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) {
switch (msg.what) {
case MSG_LOAD_ALL:
loadAllExtraInfo();
mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED);
break;
case MSG_FORCE_LOAD_PKG:
ArrayList<AppEntry> apps = mAppSession.getAllApps();
final int N = apps.size();
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)) {
updateExtraInfo(app, pkg, uid);
}
}
mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED);
break;
}
}
}
public interface Callback {
void onExtraInfoUpdated();
}
}

View File

@@ -16,156 +16,43 @@
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 {
public class AppStateNotificationBridge extends AppStateBaseBridge {
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;
Callback callback, NotificationBackend notifBackend) {
super(appState, callback);
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;
}
protected void loadAllExtraInfo() {
ArrayList<AppEntry> apps = mAppSession.getAllApps();
final int N = apps.size();
for (int i = 0; i < N; i++) {
AppEntry app = apps.get(i);
app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info);
}
}
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();
@Override
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
app.extraInfo = mNotifBackend.loadAppRow(mPm, app.info);
}
public static final AppFilter FILTER_APP_NOTIFICATION_BLOCKED = new AppFilter() {

View File

@@ -0,0 +1,265 @@
/*
* 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.Manifest;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import com.android.settings.applications.ApplicationsState.AppEntry;
import com.android.settings.applications.ApplicationsState.AppFilter;
import java.util.List;
/*
* Connects app usage info to the ApplicationsState.
* Also provides app filters that can use the info.
*/
public class AppStateUsageBridge extends AppStateBaseBridge {
private static final String TAG = "AppStateUsageBridge";
private static final String[] PM_USAGE_STATS_PERMISSION = {
Manifest.permission.PACKAGE_USAGE_STATS
};
private static final int[] APP_OPS_OP_CODES = {
AppOpsManager.OP_GET_USAGE_STATS
};
private final IPackageManager mIPackageManager;
private final UserManager mUserManager;
private final List<UserHandle> mProfiles;
private final AppOpsManager mAppOpsManager;
private final Context mContext;
public AppStateUsageBridge(Context context, ApplicationsState appState, Callback callback) {
super(appState, callback);
mContext = context;
mIPackageManager = AppGlobals.getPackageManager();
mUserManager = UserManager.get(context);
mProfiles = mUserManager.getUserProfiles();
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
private boolean isThisUserAProfileOfCurrentUser(final int userId) {
final int profilesMax = mProfiles.size();
for (int i = 0; i < profilesMax; i++) {
if (mProfiles.get(i).getIdentifier() == userId) {
return true;
}
}
return false;
}
@Override
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
app.extraInfo = getUsageInfo(pkg, uid);
}
public UsageState getUsageInfo(String pkg, int uid) {
UsageState usageState = new UsageState(pkg, new UserHandle(UserHandle.getUserId(uid)));
try {
usageState.packageInfo = mIPackageManager.getPackageInfo(pkg,
PackageManager.GET_PERMISSIONS, usageState.userHandle.getIdentifier());
// Check permission state.
String[] requestedPermissions = usageState.packageInfo.requestedPermissions;
int[] permissionFlags = usageState.packageInfo.requestedPermissionsFlags;
if (requestedPermissions != null) {
for (int i = 0; i < requestedPermissions.length; i++) {
if (Manifest.permission.PACKAGE_USAGE_STATS.equals(requestedPermissions[i])
&& (permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED)
!= 0) {
usageState.permissionGranted = true;
break;
}
}
}
// Check app op state.
List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, APP_OPS_OP_CODES);
if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) {
usageState.appOpMode = ops.get(0).getOps().get(0).getMode();
}
} catch (RemoteException e) {
Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e);
}
return usageState;
}
@Override
protected void loadAllExtraInfo() {
SparseArray<ArrayMap<String, UsageState>> entries = getEntries();
// Load state info.
loadPermissionsStates(entries);
loadAppOpsStates(entries);
// Map states to application info.
List<AppEntry> apps = mAppSession.getAllApps();
final int N = apps.size();
for (int i = 0; i < N; i++) {
AppEntry app = apps.get(i);
int userId = UserHandle.getUserId(app.info.uid);
ArrayMap<String, UsageState> userMap = entries.get(userId);
app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null;
}
}
private SparseArray<ArrayMap<String, UsageState>> getEntries() {
try {
final String[] packages = mIPackageManager.getAppOpPermissionPackages(
Manifest.permission.PACKAGE_USAGE_STATS);
if (packages == null) {
// No packages are requesting permission to use the UsageStats API.
return null;
}
SparseArray<ArrayMap<String, UsageState>> entries = new SparseArray<>();
for (final UserHandle profile : mProfiles) {
final ArrayMap<String, UsageState> entriesForProfile = new ArrayMap<>();
final int profileId = profile.getIdentifier();
entries.put(profileId, entriesForProfile);
for (final String packageName : packages) {
final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
profileId);
if (!shouldIgnorePackage(packageName) && isAvailable) {
final UsageState newEntry = new UsageState(packageName, profile);
entriesForProfile.put(packageName, newEntry);
}
}
}
return entries;
} catch (RemoteException e) {
Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
+ Manifest.permission.PACKAGE_USAGE_STATS, e);
return null;
}
}
private void loadPermissionsStates(SparseArray<ArrayMap<String, UsageState>> entries) {
// Load the packages that have been granted the PACKAGE_USAGE_STATS permission.
try {
for (final UserHandle profile : mProfiles) {
final int profileId = profile.getIdentifier();
final ArrayMap<String, UsageState> entriesForProfile = entries.get(profileId);
if (entriesForProfile == null) {
continue;
}
@SuppressWarnings("unchecked")
final List<PackageInfo> packageInfos = mIPackageManager
.getPackagesHoldingPermissions(PM_USAGE_STATS_PERMISSION, 0, profileId)
.getList();
final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
for (int i = 0; i < packageInfoCount; i++) {
final PackageInfo packageInfo = packageInfos.get(i);
final UsageState pe = entriesForProfile.get(packageInfo.packageName);
if (pe != null) {
pe.packageInfo = packageInfo;
pe.permissionGranted = true;
}
}
}
} catch (RemoteException e) {
Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
+ Manifest.permission.PACKAGE_USAGE_STATS, e);
return;
}
}
private void loadAppOpsStates(SparseArray<ArrayMap<String, UsageState>> entries) {
// Find out which packages have been granted permission from AppOps.
final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
APP_OPS_OP_CODES);
final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
for (int i = 0; i < packageOpsCount; i++) {
final AppOpsManager.PackageOps packageOp = packageOps.get(i);
final int userId = UserHandle.getUserId(packageOp.getUid());
if (!isThisUserAProfileOfCurrentUser(userId)) {
// This AppOp does not belong to any of this user's profiles.
continue;
}
final ArrayMap<String, UsageState> entriesForProfile = entries.get(userId);
if (entriesForProfile == null) {
continue;
}
final UsageState pe = entriesForProfile.get(packageOp.getPackageName());
if (pe == null) {
Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
+ " of user " + userId +
" but package doesn't exist or did not request UsageStats access");
continue;
}
if (packageOp.getOps().size() < 1) {
Log.w(TAG, "No AppOps permission exists for package "
+ packageOp.getPackageName());
continue;
}
pe.appOpMode = packageOp.getOps().get(0).getMode();
}
}
private boolean shouldIgnorePackage(String packageName) {
return packageName.equals("android") || packageName.equals(mContext.getPackageName());
}
public static class UsageState {
public final String packageName;
public final UserHandle userHandle;
public PackageInfo packageInfo;
public boolean permissionGranted;
public int appOpMode;
public UsageState(String packageName, UserHandle userHandle) {
this.packageName = packageName;
this.appOpMode = AppOpsManager.MODE_DEFAULT;
this.userHandle = userHandle;
}
public boolean hasAccess() {
if (appOpMode == AppOpsManager.MODE_DEFAULT) {
return permissionGranted;
}
return appOpMode == AppOpsManager.MODE_ALLOWED;
}
}
public static final AppFilter FILTER_APP_USAGE = new AppFilter() {
@Override
public void init() {
}
@Override
public boolean filterApp(AppEntry info) {
return info.extraInfo != null;
}
};
}

View File

@@ -58,8 +58,10 @@ import com.android.settings.Settings.AllApplicationsActivity;
import com.android.settings.Settings.DomainsURLsAppListActivity;
import com.android.settings.Settings.NotificationAppListActivity;
import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.Settings.UsageAccessSettingsActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
import com.android.settings.applications.ApplicationsState.AppEntry;
import com.android.settings.applications.ApplicationsState.AppFilter;
import com.android.settings.applications.ApplicationsState.CompoundFilter;
@@ -117,6 +119,7 @@ public class ManageApplications extends InstrumentedFragment
public static final int FILTER_APPS_PERSONAL = 9;
public static final int FILTER_APPS_WORK = 10;
public static final int FILTER_APPS_WITH_DOMAIN_URLS = 11;
public static final int FILTER_APPS_USAGE_ACCESS = 12;
// 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[] {
@@ -132,6 +135,7 @@ public class ManageApplications extends InstrumentedFragment
R.string.filter_personal_apps, // Personal
R.string.filter_work_apps, // Work
R.string.filter_with_domain_urls_apps, // Domain URLs
R.string.filter_all_apps, // Usage access screen, never displayed
};
// This is the actual mapping to filters from FILTER_ constants above, the order must
// be kept in sync.
@@ -152,6 +156,7 @@ public class ManageApplications extends InstrumentedFragment
ApplicationsState.FILTER_PERSONAL, // Personal
ApplicationsState.FILTER_WORK, // Work
ApplicationsState.FILTER_WITH_DOMAIN_URLS, // Apps with Domain URLs
AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
};
// sort order
@@ -190,6 +195,7 @@ public class ManageApplications extends InstrumentedFragment
public static final int LIST_TYPE_NOTIFICATION = 1;
public static final int LIST_TYPE_DOMAINS_URLS = 2;
public static final int LIST_TYPE_STORAGE = 3;
public static final int LIST_TYPE_USAGE_ACCESS = 4;
private View mRootView;
@@ -230,6 +236,9 @@ public class ManageApplications extends InstrumentedFragment
mListType = LIST_TYPE_MAIN;
mSortOrder = R.id.sort_order_size;
}
} else if (className.equals(UsageAccessSettingsActivity.class.getName())) {
mListType = LIST_TYPE_USAGE_ACCESS;
getActivity().getActionBar().setTitle(R.string.usage_access_title);
} else {
mListType = LIST_TYPE_MAIN;
}
@@ -300,7 +309,7 @@ public class ManageApplications extends InstrumentedFragment
contentParent.addView(mSpinnerHeader, 0);
mFilterAdapter.enableFilter(getDefaultFilter());
if (mListType != LIST_TYPE_STORAGE) {
if (mListType == LIST_TYPE_MAIN || mListType == LIST_TYPE_NOTIFICATION) {
if (UserManager.get(getActivity()).getUserProfiles().size() > 1) {
mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
mFilterAdapter.enableFilter(FILTER_APPS_WORK);
@@ -331,6 +340,8 @@ public class ManageApplications extends InstrumentedFragment
return mShowSystem ? FILTER_APPS_ALL : FILTER_APPS_DOWNLOADED_AND_LAUNCHER;
case LIST_TYPE_DOMAINS_URLS:
return FILTER_APPS_WITH_DOMAIN_URLS;
case LIST_TYPE_USAGE_ACCESS:
return FILTER_APPS_USAGE_ACCESS;
default:
return FILTER_APPS_ALL;
}
@@ -347,6 +358,8 @@ public class ManageApplications extends InstrumentedFragment
return MetricsLogger.MANAGE_DOMAIN_URLS;
case LIST_TYPE_STORAGE:
return InstrumentedFragment.VIEW_CATEGORY_STORAGE_APPS;
case LIST_TYPE_USAGE_ACCESS:
return MetricsLogger.USAGE_ACCESS;
default:
return MetricsLogger.VIEW_UNKNOWN;
}
@@ -398,7 +411,7 @@ public class ManageApplications extends InstrumentedFragment
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
if (mListType == LIST_TYPE_NOTIFICATION) {
mApplications.mNotifBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
} else {
mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
}
@@ -408,30 +421,39 @@ public class ManageApplications extends InstrumentedFragment
// utility method used to start sub activity
private void startApplicationDetailsActivity() {
Activity activity = getActivity();
if (mListType == LIST_TYPE_NOTIFICATION) {
activity.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 if (mListType == LIST_TYPE_DOMAINS_URLS) {
final String title = getString(R.string.auto_launch_label);
startAppInfoFragment(AppLaunchSettings.class, title);
} else {
switch (mListType) {
case LIST_TYPE_NOTIFICATION:
activity.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));
break;
case LIST_TYPE_DOMAINS_URLS:
startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label);
break;
case LIST_TYPE_USAGE_ACCESS:
startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
break;
case LIST_TYPE_STORAGE:
startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
break;
// 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));
activity.startActivityAsUser(intent, new UserHandle(UserHandle.getUserId(mCurrentUid)));
default:
startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label);
break;
}
}
private void startAppInfoFragment(Class<? extends AppInfoBase> fragment, CharSequence title) {
private void startAppInfoFragment(Class<? extends AppInfoBase> fragment, int titleRes) {
Bundle args = new Bundle();
args.putString("package", mCurrentPkgName);
args.putString(AppInfoBase.ARG_PACKAGE_NAME, mCurrentPkgName);
SettingsActivity sa = (SettingsActivity) getActivity();
sa.startPreferencePanel(fragment.getName(), args, -1, title, this, 0);
Intent intent = Utils.onBuildStartFragmentIntent(getActivity(), fragment.getName(), args,
null, titleRes, null, false);
getActivity().startActivityForResultAsUser(intent, INSTALLED_APP_DETAILS,
new UserHandle(UserHandle.getUserId(mCurrentUid)));
}
@Override
@@ -653,14 +675,14 @@ public class ManageApplications extends InstrumentedFragment
* The order of applications in the list is mirrored in mAppLocalList
*/
static class ApplicationsAdapter extends BaseAdapter implements Filterable,
ApplicationsState.Callbacks, AppStateNotificationBridge.Callback,
ApplicationsState.Callbacks, AppStateBaseBridge.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 final AppStateBaseBridge mExtraInfoBridge;
private int mFilterMode;
private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
private ArrayList<ApplicationsState.AppEntry> mEntries;
@@ -700,11 +722,12 @@ public class ManageApplications extends InstrumentedFragment
mPm = mContext.getPackageManager();
mFilterMode = filterMode;
if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
mNotifBridge = new AppStateNotificationBridge(
mContext.getPackageManager(), mState,
manageApplications.mNotifBackend, this);
mExtraInfoBridge = new AppStateNotificationBridge(mContext.getPackageManager(),
mState, this, manageApplications.mNotifBackend);
} else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
} else {
mNotifBridge = null;
mExtraInfoBridge = null;
}
}
@@ -724,8 +747,8 @@ public class ManageApplications extends InstrumentedFragment
mResumed = true;
mSession.resume();
mLastSortMode = sort;
if (mNotifBridge != null) {
mNotifBridge.resume();
if (mExtraInfoBridge != null) {
mExtraInfoBridge.resume();
}
rebuild(true);
} else {
@@ -737,16 +760,16 @@ public class ManageApplications extends InstrumentedFragment
if (mResumed) {
mResumed = false;
mSession.pause();
if (mNotifBridge != null) {
mNotifBridge.pause();
if (mExtraInfoBridge != null) {
mExtraInfoBridge.pause();
}
}
}
public void release() {
mSession.release();
if (mNotifBridge != null) {
mNotifBridge.release();
if (mExtraInfoBridge != null) {
mExtraInfoBridge.release();
}
}
@@ -809,6 +832,10 @@ public class ManageApplications extends InstrumentedFragment
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
mManageApplications.mListContainer, true, true);
}
if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
// No enabled or disabled filters for usage access.
return;
}
mManageApplications.setHasDisabled(hasDisabledApps());
}
@@ -849,10 +876,8 @@ public class ManageApplications extends InstrumentedFragment
}
@Override
public void onNotificationInfoUpdated() {
if (mFilterMode != mManageApplications.getDefaultFilter()) {
rebuild(false);
}
public void onExtraInfoUpdated() {
rebuild(false);
}
@Override
@@ -897,9 +922,7 @@ public class ManageApplications extends InstrumentedFragment
AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
if (holder.entry.info.packageName.equals(packageName)) {
synchronized (holder.entry) {
if (mManageApplications.mListType != LIST_TYPE_NOTIFICATION) {
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
}
updateSummary(holder);
}
if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
&& mLastSortMode == R.id.sort_order_size) {
@@ -967,24 +990,7 @@ public class ManageApplications extends InstrumentedFragment
if (entry.icon != null) {
holder.appIcon.setImageDrawable(entry.icon);
}
switch (mManageApplications.mListType) {
case LIST_TYPE_NOTIFICATION:
if (entry.extraInfo != null) {
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
(AppRow) entry.extraInfo, mContext));
} else {
holder.summary.setText("");
}
break;
case LIST_TYPE_DOMAINS_URLS:
holder.summary.setText(getDomainsSummary(entry.info.packageName));
break;
default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
break;
}
updateSummary(holder);
convertView.setEnabled(isAppEntryViewEnabled(entry));
if ((entry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
holder.disabled.setVisibility(View.VISIBLE);
@@ -1002,6 +1008,36 @@ public class ManageApplications extends InstrumentedFragment
return convertView;
}
private void updateSummary(AppViewHolder holder) {
switch (mManageApplications.mListType) {
case LIST_TYPE_NOTIFICATION:
if (holder.entry.extraInfo != null) {
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
(AppRow) holder.entry.extraInfo, mContext));
} else {
holder.summary.setText(null);
}
break;
case LIST_TYPE_DOMAINS_URLS:
holder.summary.setText(getDomainsSummary(holder.entry.info.packageName));
break;
case LIST_TYPE_USAGE_ACCESS:
if (holder.entry.extraInfo != null) {
holder.summary.setText(((UsageState) holder.entry.extraInfo).hasAccess() ?
R.string.switch_on_text : R.string.switch_off_text);
} else {
holder.summary.setText(null);
}
break;
default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
break;
}
}
@Override
public Filter getFilter() {
return mFilter;

View File

@@ -0,0 +1,148 @@
/*
* 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.app.AlertDialog;
import android.app.AppOpsManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.util.Log;
import com.android.settings.InstrumentedFragment;
import com.android.settings.R;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
OnPreferenceClickListener {
private static final String KEY_USAGE_SWITCH = "usage_switch";
private static final String KEY_USAGE_PREFS = "app_usage_preference";
// Use a bridge to get the usage stats but don't initialize it to connect with all state.
// TODO: Break out this functionality into its own class.
private AppStateUsageBridge mUsageBridge;
private AppOpsManager mAppOpsManager;
private SwitchPreference mSwitchPref;
private Preference mUsagePrefs;
private Intent mSettingsIntent;
private UsageState mUsageState;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context context = getActivity();
mUsageBridge = new AppStateUsageBridge(context, mState, null);
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
addPreferencesFromResource(R.xml.usage_access_details);
mSwitchPref = (SwitchPreference) findPreference(KEY_USAGE_SWITCH);
mUsagePrefs = findPreference(KEY_USAGE_PREFS);
mSwitchPref.setOnPreferenceChangeListener(this);
mUsagePrefs.setOnPreferenceClickListener(this);
mSettingsIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Settings.INTENT_CATEGORY_USAGE_ACCESS_CONFIG)
.setPackage(mPackageName);
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mUsagePrefs) {
if (mSettingsIntent != null) {
try {
getActivity().startActivityAsUser(mSettingsIntent, new UserHandle(mUserId));
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Unable to launch app usage access settings " + mSettingsIntent, e);
}
}
return true;
}
return false;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSwitchPref) {
if (mUsageState != null && (Boolean) newValue != mUsageState.hasAccess()) {
setHasAccess(!mUsageState.hasAccess());
refreshUi();
}
return true;
}
return false;
}
private void setHasAccess(boolean newState) {
mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS, mPackageInfo.applicationInfo.uid,
mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
}
@Override
protected boolean refreshUi() {
mUsageState = mUsageBridge.getUsageInfo(mPackageName,
mPackageInfo.applicationInfo.uid);
boolean hasAccess = mUsageState.hasAccess();
mSwitchPref.setChecked(hasAccess);
mUsagePrefs.setEnabled(hasAccess);
ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
PackageManager.GET_META_DATA, mUserId);
if (resolveInfo != null) {
if (findPreference(KEY_USAGE_PREFS) == null) {
getPreferenceScreen().addPreference(mUsagePrefs);
}
Bundle metaData = resolveInfo.activityInfo.metaData;
mSettingsIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name));
if (metaData != null
&& metaData.containsKey(Settings.METADATA_USAGE_ACCESS_REASON)) {
mSwitchPref.setSummary(
metaData.getString(Settings.METADATA_USAGE_ACCESS_REASON));
}
} else {
if (findPreference(KEY_USAGE_PREFS) != null) {
getPreferenceScreen().removePreference(mUsagePrefs);
}
}
return true;
}
@Override
protected AlertDialog createDialog(int id, int errorCode) {
return null;
}
@Override
protected int getMetricsCategory() {
return InstrumentedFragment.VIEW_CATEGORY_USAGE_ACCESS_DETAIL;
}
}