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
diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java
index 883c67c..d239d4e 100644
--- a/src/com/android/settings/InstrumentedFragment.java
+++ b/src/com/android/settings/InstrumentedFragment.java
@@ -28,6 +28,7 @@
 
     public static final int VIEW_CATEGORY_DEFAULT_APPS = VIEW_CATEGORY_UNDECLARED + 1;
     public static final int VIEW_CATEGORY_STORAGE_APPS = VIEW_CATEGORY_UNDECLARED + 2;
+    public static final int VIEW_CATEGORY_USAGE_ACCESS_DETAIL = VIEW_CATEGORY_UNDECLARED + 3;
 
     /**
      * Declare the view of this category.
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index e8be34a..caf2491 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -76,6 +76,7 @@
 import com.android.settings.applications.InstalledAppDetails;
 import com.android.settings.applications.ManageApplications;
 import com.android.settings.applications.ProcessStatsUi;
+import com.android.settings.applications.UsageAccessDetails;
 import com.android.settings.bluetooth.BluetoothSettings;
 import com.android.settings.dashboard.DashboardCategory;
 import com.android.settings.dashboard.DashboardSummary;
@@ -300,7 +301,7 @@
             NotificationStation.class.getName(),
             LocationSettings.class.getName(),
             SecuritySettings.class.getName(),
-            UsageAccessSettings.class.getName(),
+            UsageAccessDetails.class.getName(),
             PrivacySettings.class.getName(),
             DeviceAdminSettings.class.getName(),
             AccessibilitySettings.class.getName(),
diff --git a/src/com/android/settings/UsageAccessSettings.java b/src/com/android/settings/UsageAccessSettings.java
deleted file mode 100644
index 704c3c5..0000000
--- a/src/com/android/settings/UsageAccessSettings.java
+++ /dev/null
@@ -1,569 +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;
-
-import com.android.internal.content.PackageMonitor;
-import android.Manifest;
-import android.app.ActivityThread;
-import android.app.AlertDialog;
-import android.app.AppOpsManager;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.preference.Preference;
-import android.preference.PreferenceScreen;
-import android.preference.SwitchPreference;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-import com.android.internal.logging.MetricsLogger;
-
-import java.util.List;
-import java.util.Collections;
-
-public class UsageAccessSettings extends SettingsPreferenceFragment implements
-        Preference.OnPreferenceChangeListener {
-
-    private static final String TAG = "UsageAccessSettings";
-    private static final String BUNDLE_KEY_PROFILEID = "profileId";
-
-    private static final String[] PM_USAGE_STATS_PERMISSION = new String[] {
-            Manifest.permission.PACKAGE_USAGE_STATS
-    };
-
-    private static final int[] APP_OPS_OP_CODES = new int[] {
-            AppOpsManager.OP_GET_USAGE_STATS
-    };
-
-    private static class PackageEntry implements Comparable<PackageEntry> {
-        public PackageEntry(String packageName, UserHandle userHandle) {
-            this.packageName = packageName;
-            this.appOpMode = AppOpsManager.MODE_DEFAULT;
-            this.userHandle = userHandle;
-        }
-
-        @Override
-        public int compareTo(PackageEntry another) {
-            return packageName.compareTo(another.packageName);
-        }
-
-        final String packageName;
-        PackageInfo packageInfo;
-        boolean permissionGranted;
-        int appOpMode;
-        UserHandle userHandle;
-
-        SwitchPreference preference;
-    }
-
-    /**
-     * Fetches the list of Apps that are requesting access to the UsageStats API and updates
-     * the PreferenceScreen with the results when complete.
-     */
-    private class AppsRequestingAccessFetcher extends
-            AsyncTask<Void, Void, SparseArray<ArrayMap<String, PackageEntry>>> {
-
-        private final Context mContext;
-        private final PackageManager mPackageManager;
-        private final IPackageManager mIPackageManager;
-        private final UserManager mUserManager;
-        private final List<UserHandle> mProfiles;
-
-        public AppsRequestingAccessFetcher(Context context) {
-            mContext = context;
-            mPackageManager = context.getPackageManager();
-            mIPackageManager = ActivityThread.getPackageManager();
-            mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            mProfiles = mUserManager.getUserProfiles();
-        }
-
-        @Override
-        protected SparseArray<ArrayMap<String, PackageEntry>> doInBackground(Void... params) {
-            final String[] packages;
-            SparseArray<ArrayMap<String, PackageEntry>> entries;
-            try {
-                packages = mIPackageManager.getAppOpPermissionPackages(
-                        Manifest.permission.PACKAGE_USAGE_STATS);
-
-                if (packages == null) {
-                    // No packages are requesting permission to use the UsageStats API.
-                    return null;
-                }
-
-                entries = new SparseArray<>();
-                for (final UserHandle profile : mProfiles) {
-                    final ArrayMap<String, PackageEntry> 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 PackageEntry newEntry = new PackageEntry(packageName, profile);
-                            entriesForProfile.put(packageName, newEntry);
-                        }
-                    }
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
-                        + Manifest.permission.PACKAGE_USAGE_STATS);
-                return null;
-            }
-
-             // 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, PackageEntry> entriesForProfile = entries.get(profileId);
-                    if (entriesForProfile == null) {
-                        continue;
-                    }
-                    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 PackageEntry 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);
-                return null;
-            }
-
-            // Load the remaining packages that have requested but don't have the
-            // PACKAGE_USAGE_STATS permission.
-            for (final UserHandle profile : mProfiles) {
-                final int profileId = profile.getIdentifier();
-                final ArrayMap<String, PackageEntry> entriesForProfile = entries.get(profileId);
-                if (entriesForProfile == null) {
-                    continue;
-                }
-                int packageCount = entriesForProfile.size();
-                for (int i = packageCount - 1; i >= 0; --i) {
-                    final PackageEntry pe = entriesForProfile.valueAt(i);
-                    if (pe.packageInfo == null) {
-                        try {
-                            pe.packageInfo = mIPackageManager.getPackageInfo(pe.packageName, 0,
-                                    profileId);
-                        } catch (RemoteException e) {
-                            // This package doesn't exist. This may occur when an app is
-                            // uninstalled for one user, but it is not removed from the system.
-                            entriesForProfile.removeAt(i);
-                        }
-                    }
-                }
-            }
-
-            // 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, PackageEntry> entriesForProfile = entries.get(userId);
-                if (entriesForProfile == null) {
-                    continue;
-                }
-                final PackageEntry 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();
-            }
-
-            return entries;
-        }
-
-        @Override
-        protected void onPostExecute(SparseArray<ArrayMap<String, PackageEntry>> newEntries) {
-            mLastFetcherTask = null;
-
-            if (getActivity() == null) {
-                // We must have finished the Activity while we were processing in the background.
-                return;
-            }
-
-            if (newEntries == null) {
-                mPackageEntryMap.clear();
-                mPreferenceScreen.removeAll();
-                return;
-            }
-
-            // Find the deleted entries and remove them from the PreferenceScreen.
-            final int oldProfileCount = mPackageEntryMap.size();
-            for (int profileIndex = 0; profileIndex < oldProfileCount; ++profileIndex) {
-                final int profileId = mPackageEntryMap.keyAt(profileIndex);
-                final ArrayMap<String, PackageEntry> oldEntriesForProfile = mPackageEntryMap
-                        .valueAt(profileIndex);
-                final int oldPackageCount = oldEntriesForProfile.size();
-
-                final ArrayMap<String, PackageEntry> newEntriesForProfile = newEntries.get(
-                        profileId);
-
-                for (int i = 0; i < oldPackageCount; i++) {
-                    final PackageEntry oldPackageEntry = oldEntriesForProfile.valueAt(i);
-
-                    PackageEntry newPackageEntry = null;
-                    if (newEntriesForProfile != null) {
-                        newPackageEntry = newEntriesForProfile.get(oldPackageEntry.packageName);
-                    }
-                    if (newPackageEntry == null) {
-                        // This package has been removed.
-                        mPreferenceScreen.removePreference(oldPackageEntry.preference);
-                    } else {
-                        // This package already exists in the preference hierarchy, so reuse that
-                        // Preference.
-                        newPackageEntry.preference = oldPackageEntry.preference;
-                    }
-                }
-            }
-
-            // Now add new packages to the PreferenceScreen.
-            final int newProfileCount = newEntries.size();
-            for (int profileIndex = 0; profileIndex < newProfileCount; ++profileIndex) {
-                final int profileId = newEntries.keyAt(profileIndex);
-                final ArrayMap<String, PackageEntry> newEntriesForProfile = newEntries.get(
-                        profileId);
-                final int packageCount = newEntriesForProfile.size();
-                for (int i = 0; i < packageCount; i++) {
-                    final PackageEntry packageEntry = newEntriesForProfile.valueAt(i);
-                    if (packageEntry.preference == null) {
-                        packageEntry.preference = new SwitchPreference(mContext);
-                        packageEntry.preference.setPersistent(false);
-                        packageEntry.preference.setOnPreferenceChangeListener(
-                                UsageAccessSettings.this);
-                        mPreferenceScreen.addPreference(packageEntry.preference);
-                    }
-                    updatePreference(packageEntry);
-                }
-            }
-            mPackageEntryMap.clear();
-            mPackageEntryMap = newEntries;
-
-            // Add/remove headers if necessary. If there are package entries only for one user and
-            // that user is not the managed profile then do not show headers.
-            if (mPackageEntryMap.size() == 1 &&
-                    mPackageEntryMap.keyAt(0) == UserHandle.myUserId()) {
-                for (int i = 0; i < mCategoryHeaders.length; ++i) {
-                    if (mCategoryHeaders[i] != null) {
-                        mPreferenceScreen.removePreference(mCategoryHeaders[i]);
-                    }
-                    mCategoryHeaders[i] = null;
-                }
-            } else {
-                for (int i = 0; i < mCategoryHeaders.length; ++i) {
-                    if (mCategoryHeaders[i] == null) {
-                        final Preference preference = new Preference(mContext, null,
-                                com.android.internal.R.attr.preferenceCategoryStyle, 0);
-                        mCategoryHeaders[i] = preference;
-                        preference.setTitle(mCategoryHeaderTitleResIds[i]);
-                        preference.setEnabled(false);
-                        mPreferenceScreen.addPreference(preference);
-                    }
-                }
-            }
-
-            // Sort preferences alphabetically within categories
-            int order = 0;
-            final int profileCount = mProfiles.size();
-            for (int i = 0; i < profileCount; ++i) {
-                Preference header = mCategoryHeaders[i];
-                if (header != null) {
-                    header.setOrder(order++);
-                }
-                ArrayMap<String, PackageEntry> entriesForProfile =
-                        mPackageEntryMap.get(mProfiles.get(i).getIdentifier());
-                if (entriesForProfile != null) {
-                    List<PackageEntry> sortedEntries = Collections.list(
-                            Collections.enumeration(entriesForProfile.values()));
-                    Collections.sort(sortedEntries);
-                    for (PackageEntry pe : sortedEntries) {
-                        pe.preference.setOrder(order++);
-                    }
-                }
-            }
-        }
-
-        private void updatePreference(PackageEntry pe) {
-            final int profileId = pe.userHandle.getIdentifier();
-            // Set something as default
-            pe.preference.setEnabled(false);
-            pe.preference.setTitle(pe.packageName);
-            pe.preference.setIcon(mUserManager.getBadgedIconForUser(mPackageManager
-                    .getDefaultActivityIcon(), pe.userHandle));
-            try {
-                // Try setting real title and icon
-                final ApplicationInfo info = mIPackageManager.getApplicationInfo(pe.packageName,
-                        0 /* no flags */, profileId);
-                if (info != null) {
-                    pe.preference.setEnabled(true);
-                    pe.preference.setTitle(info.loadLabel(mPackageManager).toString());
-                    pe.preference.setIcon(mUserManager.getBadgedIconForUser(info.loadIcon(
-                            mPackageManager), pe.userHandle));
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "PackageManager is dead. Can't get app info for package " +
-                        pe.packageName + " of user " + profileId);
-                // Keep going to update other parts of the preference
-            }
-
-            pe.preference.setKey(pe.packageName);
-            Bundle extra = pe.preference.getExtras();
-            extra.putInt(BUNDLE_KEY_PROFILEID, profileId);
-
-            boolean check = false;
-            if (pe.appOpMode == AppOpsManager.MODE_ALLOWED) {
-                check = true;
-            } else if (pe.appOpMode == AppOpsManager.MODE_DEFAULT) {
-                // If the default AppOps mode is set, then fall back to
-                // whether the app has been granted permission by PackageManager.
-                check = pe.permissionGranted;
-            }
-
-            if (check != pe.preference.isChecked()) {
-                pe.preference.setChecked(check);
-            }
-        }
-
-        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;
-        }
-    }
-
-    static boolean shouldIgnorePackage(String packageName) {
-        return packageName.equals("android") || packageName.equals("com.android.settings");
-    }
-
-    private AppsRequestingAccessFetcher mLastFetcherTask;
-    SparseArray<ArrayMap<String, PackageEntry>> mPackageEntryMap = new SparseArray<>();
-    AppOpsManager mAppOpsManager;
-    PreferenceScreen mPreferenceScreen;
-    private Preference[] mCategoryHeaders = new Preference[2];
-    private static int[] mCategoryHeaderTitleResIds = new int[] {
-        R.string.category_personal,
-        R.string.category_work
-    };
-
-    @Override
-    protected int getMetricsCategory() {
-        return MetricsLogger.USAGE_ACCESS;
-    }
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        addPreferencesFromResource(R.xml.usage_access_settings);
-        mPreferenceScreen = getPreferenceScreen();
-        mPreferenceScreen.setOrderingAsAdded(false);
-        mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        updateInterestedApps();
-        mPackageMonitor.register(getActivity(), Looper.getMainLooper(), false);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-
-        mPackageMonitor.unregister();
-        if (mLastFetcherTask != null) {
-            mLastFetcherTask.cancel(true);
-            mLastFetcherTask = null;
-        }
-    }
-
-    private void updateInterestedApps() {
-        if (mLastFetcherTask != null) {
-            // Canceling can only fail for some obscure reason since mLastFetcherTask would be
-            // null if the task has already completed. So we ignore the result of cancel and
-            // spawn a new task to get fresh data. AsyncTask executes tasks serially anyways,
-            // so we are safe from running two tasks at the same time.
-            mLastFetcherTask.cancel(true);
-        }
-
-        mLastFetcherTask = new AppsRequestingAccessFetcher(getActivity());
-        mLastFetcherTask.execute();
-    }
-
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        final String packageName = preference.getKey();
-        final int profileId = preference.getExtras().getInt(BUNDLE_KEY_PROFILEID);
-        final PackageEntry pe = getPackageEntry(packageName, profileId);
-        if (pe == null) {
-            Log.w(TAG, "Preference change event handling failed");
-            return false;
-        }
-
-        if (!(newValue instanceof Boolean)) {
-            Log.w(TAG, "Preference change event for package " + packageName + " of user " +
-                    profileId + " had non boolean value of type " + newValue.getClass().getName());
-            return false;
-        }
-
-        final int newMode = (Boolean) newValue ?
-                AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
-
-        // Check if we need to do any work.
-        if (pe.appOpMode != newMode) {
-            if (newMode != AppOpsManager.MODE_ALLOWED) {
-                // Turning off the setting has no warning.
-                setNewMode(pe, newMode);
-                return true;
-            }
-
-            // Turning on the setting has a Warning.
-            FragmentTransaction ft = getChildFragmentManager().beginTransaction();
-            Fragment prev = getChildFragmentManager().findFragmentByTag("warning");
-            if (prev != null) {
-                ft.remove(prev);
-            }
-            WarningDialogFragment.newInstance(pe).show(ft, "warning");
-            return false;
-        }
-        return true;
-    }
-
-    void setNewMode(PackageEntry pe, int newMode) {
-        mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS,
-        pe.packageInfo.applicationInfo.uid, pe.packageName, newMode);
-        pe.appOpMode = newMode;
-    }
-
-    void allowAccess(String packageName, int profileId) {
-        final PackageEntry entry = getPackageEntry(packageName, profileId);
-        if (entry == null) {
-            Log.w(TAG, "Unable to give access");
-            return;
-        }
-
-        setNewMode(entry, AppOpsManager.MODE_ALLOWED);
-        entry.preference.setChecked(true);
-    }
-
-    private PackageEntry getPackageEntry(String packageName, int profileId) {
-        ArrayMap<String, PackageEntry> entriesForProfile = mPackageEntryMap.get(profileId);
-        if (entriesForProfile == null) {
-            Log.w(TAG, "getPackageEntry fails for package " + packageName + " of user " +
-                    profileId + ": user does not seem to be valid.");
-            return null;
-        }
-        final PackageEntry entry = entriesForProfile.get(packageName);
-        if (entry == null) {
-            Log.w(TAG, "getPackageEntry fails for package " + packageName + " of user " +
-                    profileId + ": package does not exist.");
-        }
-        return entry;
-    }
-
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
-        @Override
-        public void onPackageAdded(String packageName, int uid) {
-            updateInterestedApps();
-        }
-
-        @Override
-        public void onPackageRemoved(String packageName, int uid) {
-            updateInterestedApps();
-        }
-    };
-
-    public static class WarningDialogFragment extends DialogFragment
-            implements DialogInterface.OnClickListener {
-        private static final String ARG_PACKAGE_NAME = "package";
-        private static final String ARG_PROFILE_ID = "profileId";
-
-        public static WarningDialogFragment newInstance(PackageEntry pe) {
-            WarningDialogFragment dialog = new WarningDialogFragment();
-            Bundle args = new Bundle();
-            args.putString(ARG_PACKAGE_NAME, pe.packageName);
-            args.putInt(ARG_PROFILE_ID, pe.userHandle.getIdentifier());
-            dialog.setArguments(args);
-            return dialog;
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            return new AlertDialog.Builder(getActivity())
-                    .setTitle(R.string.allow_usage_access_title)
-                    .setMessage(R.string.allow_usage_access_message)
-                    .setIconAttribute(android.R.attr.alertDialogIcon)
-                    .setNegativeButton(R.string.cancel, this)
-                    .setPositiveButton(android.R.string.ok, this)
-                    .create();
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            if (which == DialogInterface.BUTTON_POSITIVE) {
-                ((UsageAccessSettings) getParentFragment()).allowAccess(
-                        getArguments().getString(ARG_PACKAGE_NAME),
-                        getArguments().getInt(ARG_PROFILE_ID));
-            } else {
-                dialog.cancel();
-            }
-        }
-    }
-}
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
index a7dc500..56fe885 100644
--- a/src/com/android/settings/applications/AppInfoBase.java
+++ b/src/com/android/settings/applications/AppInfoBase.java
@@ -32,7 +32,6 @@
 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;
diff --git a/src/com/android/settings/applications/AppStateBaseBridge.java b/src/com/android/settings/applications/AppStateBaseBridge.java
new file mode 100644
index 0000000..04eb77b
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateBaseBridge.java
@@ -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();
+    }
+}
diff --git a/src/com/android/settings/applications/AppStateNotificationBridge.java b/src/com/android/settings/applications/AppStateNotificationBridge.java
index 0434c0e..1aa7ebf 100644
--- a/src/com/android/settings/applications/AppStateNotificationBridge.java
+++ b/src/com/android/settings/applications/AppStateNotificationBridge.java
@@ -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() {
diff --git a/src/com/android/settings/applications/AppStateUsageBridge.java b/src/com/android/settings/applications/AppStateUsageBridge.java
new file mode 100644
index 0000000..22f19b2
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateUsageBridge.java
@@ -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;
+        }
+    };
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 1502173..273bcfe 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -58,8 +58,10 @@
 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 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 @@
         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 @@
         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 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 @@
                 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 @@
         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 @@
                 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 @@
                 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 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 @@
     // 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 @@
      * 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 @@
             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 @@
                 mResumed = true;
                 mSession.resume();
                 mLastSortMode = sort;
-                if (mNotifBridge != null) {
-                    mNotifBridge.resume();
+                if (mExtraInfoBridge != null) {
+                    mExtraInfoBridge.resume();
                 }
                 rebuild(true);
             } else {
@@ -737,16 +760,16 @@
             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 @@
                 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 @@
         }
 
         @Override
-        public void onNotificationInfoUpdated() {
-            if (mFilterMode != mManageApplications.getDefaultFilter()) {
-                rebuild(false);
-            }
+        public void onExtraInfoUpdated() {
+            rebuild(false);
         }
 
         @Override
@@ -897,9 +922,7 @@
                 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 @@
                 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 @@
             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;
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
new file mode 100644
index 0000000..a11d7a7
--- /dev/null
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -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;
+    }
+
+}