summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2013-01-15 19:02:05 -0800
committerDianne Hackborn <hackbod@google.com>2013-01-15 19:20:19 -0800
commita522a8ef8233382bbd39308b7d5f2e26eed44d22 (patch)
treef0a85bd706514eca1aa2cd53b5812e10b2b51138 /src
parent6001a7b0d494ca7ecad3301a96f396c255a94c7b (diff)
downloadpackages_apps_Settings-a522a8ef8233382bbd39308b7d5f2e26eed44d22.zip
packages_apps_Settings-a522a8ef8233382bbd39308b7d5f2e26eed44d22.tar.gz
packages_apps_Settings-a522a8ef8233382bbd39308b7d5f2e26eed44d22.tar.bz2
Quick and dirty UI for viewing app op information.
Change-Id: If17bfbe84cf438ca9bb37bf446564f39de99cee1
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/Settings.java1
-rw-r--r--src/com/android/settings/applications/AppOpsCategory.java487
-rw-r--r--src/com/android/settings/applications/AppOpsSummary.java106
3 files changed, 594 insertions, 0 deletions
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index f850f39..149561d 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -805,6 +805,7 @@ public class Settings extends PreferenceActivity
public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }
public static class ApplicationSettingsActivity extends Settings { /* empty */ }
public static class ManageApplicationsActivity extends Settings { /* empty */ }
+ public static class AppOpsSummaryActivity extends Settings { /* empty */ }
public static class StorageUseActivity extends Settings { /* empty */ }
public static class DevelopmentSettingsActivity extends Settings { /* empty */ }
public static class AccessibilitySettingsActivity extends Settings { /* empty */ }
diff --git a/src/com/android/settings/applications/AppOpsCategory.java b/src/com/android/settings/applications/AppOpsCategory.java
new file mode 100644
index 0000000..e9254f9
--- /dev/null
+++ b/src/com/android/settings/applications/AppOpsCategory.java
@@ -0,0 +1,487 @@
+package com.android.settings.applications;
+
+import android.app.AppOpsManager;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.Loader;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+import com.android.settings.R;
+
+public class AppOpsCategory extends ListFragment implements
+ LoaderManager.LoaderCallbacks<List<AppOpsCategory.AppOpEntry>> {
+
+ // This is the Adapter being used to display the list's data.
+ AppListAdapter mAdapter;
+
+ /**
+ * This class holds the per-item data in our Loader.
+ */
+ public static class AppEntry {
+ private final AppListLoader mLoader;
+ private final ApplicationInfo mInfo;
+ private final File mApkFile;
+ private String mLabel;
+ private Drawable mIcon;
+ private boolean mMounted;
+
+ public AppEntry(AppListLoader loader, ApplicationInfo info) {
+ mLoader = loader;
+ mInfo = info;
+ mApkFile = new File(info.sourceDir);
+ }
+
+ public ApplicationInfo getApplicationInfo() {
+ return mInfo;
+ }
+
+ public String getLabel() {
+ return mLabel;
+ }
+
+ public Drawable getIcon() {
+ if (mIcon == null) {
+ if (mApkFile.exists()) {
+ mIcon = mInfo.loadIcon(mLoader.mPm);
+ return mIcon;
+ } else {
+ mMounted = false;
+ }
+ } else if (!mMounted) {
+ // If the app wasn't mounted but is now mounted, reload
+ // its icon.
+ if (mApkFile.exists()) {
+ mMounted = true;
+ mIcon = mInfo.loadIcon(mLoader.mPm);
+ return mIcon;
+ }
+ } else {
+ return mIcon;
+ }
+
+ return mLoader.getContext().getResources().getDrawable(
+ android.R.drawable.sym_def_app_icon);
+ }
+
+ @Override public String toString() {
+ return mLabel;
+ }
+
+ void loadLabel(Context context) {
+ if (mLabel == null || !mMounted) {
+ if (!mApkFile.exists()) {
+ mMounted = false;
+ mLabel = mInfo.packageName;
+ } else {
+ mMounted = true;
+ CharSequence label = mInfo.loadLabel(context.getPackageManager());
+ mLabel = label != null ? label.toString() : mInfo.packageName;
+ }
+ }
+ }
+ }
+
+ public AppOpsCategory() {
+ }
+
+ public AppOpsCategory(int[] ops) {
+ Bundle args = new Bundle();
+ args.putIntArray("ops", ops);
+ setArguments(args);
+ }
+
+ /**
+ * This class holds the per-item data in our Loader.
+ */
+ public static class AppOpEntry {
+ private final AppOpsManager.PackageOps mPkgOps;
+ private final AppOpsManager.OpEntry mOp;
+ private final AppEntry mApp;
+
+ public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app) {
+ mPkgOps = pkg;
+ mOp = op;
+ mApp = app;
+ }
+
+ public AppEntry getAppEntry() {
+ return mApp;
+ }
+
+ public AppOpsManager.PackageOps getPackageOps() {
+ return mPkgOps;
+ }
+
+ public AppOpsManager.OpEntry getOpEntry() {
+ return mOp;
+ }
+
+ public long getTime() {
+ return mOp.getTime();
+ }
+
+ @Override public String toString() {
+ return mApp.getLabel();
+ }
+ }
+
+ /**
+ * Perform alphabetical comparison of application entry objects.
+ */
+ public static final Comparator<AppOpEntry> APP_OP_COMPARATOR = new Comparator<AppOpEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppOpEntry object1, AppOpEntry object2) {
+ if (object1.getOpEntry().isRunning() != object2.getOpEntry().isRunning()) {
+ // Currently running ops go first.
+ return object1.getOpEntry().isRunning() ? -1 : 1;
+ }
+ if (object1.getTime() != object2.getTime()) {
+ // More recent times go first.
+ return object1.getTime() > object2.getTime() ? -1 : 1;
+ }
+ return sCollator.compare(object1.getAppEntry().getLabel(),
+ object2.getAppEntry().getLabel());
+ }
+ };
+
+ /**
+ * Helper for determining if the configuration has changed in an interesting
+ * way so we need to rebuild the app list.
+ */
+ public static class InterestingConfigChanges {
+ final Configuration mLastConfiguration = new Configuration();
+ int mLastDensity;
+
+ boolean applyNewConfig(Resources res) {
+ int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+ boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
+ if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
+ |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
+ mLastDensity = res.getDisplayMetrics().densityDpi;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Helper class to look for interesting changes to the installed apps
+ * so that the loader can be updated.
+ */
+ public static class PackageIntentReceiver extends BroadcastReceiver {
+ final AppListLoader mLoader;
+
+ public PackageIntentReceiver(AppListLoader loader) {
+ mLoader = loader;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ mLoader.getContext().registerReceiver(this, filter);
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mLoader.getContext().registerReceiver(this, sdFilter);
+ }
+
+ @Override public void onReceive(Context context, Intent intent) {
+ // Tell the loader about the change.
+ mLoader.onContentChanged();
+ }
+ }
+
+ /**
+ * A custom Loader that loads all of the installed applications.
+ */
+ public static class AppListLoader extends AsyncTaskLoader<List<AppOpEntry>> {
+ final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
+ final AppOpsManager mAppOps;
+ final PackageManager mPm;
+ final int[] mOps;
+
+ final HashMap<String, AppEntry> mAppEntries = new HashMap<String, AppEntry>();
+
+ List<AppOpEntry> mApps;
+ PackageIntentReceiver mPackageObserver;
+
+ public AppListLoader(Context context, int[] ops) {
+ super(context);
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ mPm = context.getPackageManager();
+ mOps = ops;
+ }
+
+ @Override public List<AppOpEntry> loadInBackground() {
+ final Context context = getContext();
+
+ List<AppOpsManager.PackageOps> pkgs = mAppOps.getPackagesForOps(mOps);
+ List<AppOpEntry> entries = new ArrayList<AppOpEntry>(pkgs.size());
+ for (int i=0; i<pkgs.size(); i++) {
+ AppOpsManager.PackageOps pkgOps = pkgs.get(i);
+ AppEntry appEntry = mAppEntries.get(pkgOps.getPackageName());
+ if (appEntry == null) {
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mPm.getApplicationInfo(pkgOps.getPackageName(),
+ PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ appEntry = new AppEntry(this, appInfo);
+ appEntry.loadLabel(context);
+ mAppEntries.put(pkgOps.getPackageName(), appEntry);
+ }
+ for (int j=0; j<pkgOps.getOps().size(); j++) {
+ AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(j);
+ AppOpEntry entry = new AppOpEntry(pkgOps, opEntry, appEntry);
+ entries.add(entry);
+ }
+ }
+
+ // Sort the list.
+ Collections.sort(entries, APP_OP_COMPARATOR);
+
+ // Done!
+ return entries;
+ }
+
+ /**
+ * Called when there is new data to deliver to the client. The
+ * super class will take care of delivering it; the implementation
+ * here just adds a little more logic.
+ */
+ @Override public void deliverResult(List<AppOpEntry> apps) {
+ if (isReset()) {
+ // An async query came in while the loader is stopped. We
+ // don't need the result.
+ if (apps != null) {
+ onReleaseResources(apps);
+ }
+ }
+ List<AppOpEntry> oldApps = apps;
+ mApps = apps;
+
+ if (isStarted()) {
+ // If the Loader is currently started, we can immediately
+ // deliver its results.
+ super.deliverResult(apps);
+ }
+
+ // At this point we can release the resources associated with
+ // 'oldApps' if needed; now that the new result is delivered we
+ // know that it is no longer in use.
+ if (oldApps != null) {
+ onReleaseResources(oldApps);
+ }
+ }
+
+ /**
+ * Handles a request to start the Loader.
+ */
+ @Override protected void onStartLoading() {
+ if (mApps != null) {
+ // If we currently have a result available, deliver it
+ // immediately.
+ deliverResult(mApps);
+ }
+
+ // Start watching for changes in the app data.
+ if (mPackageObserver == null) {
+ mPackageObserver = new PackageIntentReceiver(this);
+ }
+
+ // Has something interesting in the configuration changed since we
+ // last built the app list?
+ boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
+
+ if (takeContentChanged() || mApps == null || configChange) {
+ // If the data has changed since the last time it was loaded
+ // or is not currently available, start a load.
+ forceLoad();
+ }
+ }
+
+ /**
+ * Handles a request to stop the Loader.
+ */
+ @Override protected void onStopLoading() {
+ // Attempt to cancel the current load task if possible.
+ cancelLoad();
+ }
+
+ /**
+ * Handles a request to cancel a load.
+ */
+ @Override public void onCanceled(List<AppOpEntry> apps) {
+ super.onCanceled(apps);
+
+ // At this point we can release the resources associated with 'apps'
+ // if needed.
+ onReleaseResources(apps);
+ }
+
+ /**
+ * Handles a request to completely reset the Loader.
+ */
+ @Override protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ // At this point we can release the resources associated with 'apps'
+ // if needed.
+ if (mApps != null) {
+ onReleaseResources(mApps);
+ mApps = null;
+ }
+
+ // Stop monitoring for changes.
+ if (mPackageObserver != null) {
+ getContext().unregisterReceiver(mPackageObserver);
+ mPackageObserver = null;
+ }
+ }
+
+ /**
+ * Helper function to take care of releasing resources associated
+ * with an actively loaded data set.
+ */
+ protected void onReleaseResources(List<AppOpEntry> apps) {
+ // For a simple List<> there is nothing to do. For something
+ // like a Cursor, we would close it here.
+ }
+ }
+
+ public static class AppListAdapter extends ArrayAdapter<AppOpEntry> {
+ private final LayoutInflater mInflater;
+ private final CharSequence[] mOpNames;
+ private final CharSequence mRunningStr;
+
+ public AppListAdapter(Context context) {
+ super(context, android.R.layout.simple_list_item_2);
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mOpNames = context.getResources().getTextArray(R.array.app_ops_names);
+ mRunningStr = context.getResources().getText(R.string.app_ops_running);
+ }
+
+ public void setData(List<AppOpEntry> data) {
+ clear();
+ if (data != null) {
+ addAll(data);
+ }
+ }
+
+ CharSequence opTimeToString(AppOpsManager.OpEntry op) {
+ if (op.isRunning()) {
+ return "Running";
+ }
+ return DateUtils.getRelativeTimeSpanString(op.getTime(),
+ System.currentTimeMillis(),
+ DateUtils.MINUTE_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE);
+ }
+
+ /**
+ * Populate new items in the list.
+ */
+ @Override public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.app_ops_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ AppOpEntry item = getItem(position);
+ ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(
+ item.getAppEntry().getIcon());
+ ((TextView)view.findViewById(R.id.app_name)).setText(item.getAppEntry().getLabel());
+ ((TextView)view.findViewById(R.id.op_name)).setText(
+ mOpNames[item.getOpEntry().getOp()]);
+ ((TextView)view.findViewById(R.id.op_time)).setText(opTimeToString(item.getOpEntry()));
+
+ return view;
+ }
+ }
+
+ @Override public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Give some text to display if there is no data. In a real
+ // application this would come from a resource.
+ setEmptyText("No applications");
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new AppListAdapter(getActivity());
+ setListAdapter(mAdapter);
+
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override public void onListItemClick(ListView l, View v, int position, long id) {
+ // Insert desired behavior here.
+ Log.i("LoaderCustom", "Item clicked: " + id);
+ }
+
+ @Override public Loader<List<AppOpEntry>> onCreateLoader(int id, Bundle args) {
+ Bundle fargs = getArguments();
+ return new AppListLoader(getActivity(), fargs != null ? fargs.getIntArray("ops") : null);
+ }
+
+ @Override public void onLoadFinished(Loader<List<AppOpEntry>> loader, List<AppOpEntry> data) {
+ // Set the new data in the adapter.
+ mAdapter.setData(data);
+
+ // The list should now be shown.
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
+ }
+
+ @Override public void onLoaderReset(Loader<List<AppOpEntry>> loader) {
+ // Clear the data in the adapter.
+ mAdapter.setData(null);
+ }
+}
diff --git a/src/com/android/settings/applications/AppOpsSummary.java b/src/com/android/settings/applications/AppOpsSummary.java
new file mode 100644
index 0000000..dd34679
--- /dev/null
+++ b/src/com/android/settings/applications/AppOpsSummary.java
@@ -0,0 +1,106 @@
+package com.android.settings.applications;
+
+import android.app.AppOpsManager;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.preference.PreferenceFrameLayout;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.PagerTabStrip;
+import android.support.v4.view.ViewPager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.settings.R;
+
+public class AppOpsSummary extends Fragment {
+ // layout inflater object used to inflate views
+ private LayoutInflater mInflater;
+
+ private ViewGroup mContentContainer;
+ private View mRootView;
+ private ViewPager mViewPager;
+
+ CharSequence[] mPageNames;
+ static int[][] sPageOps = new int[][] {
+ // "Location" page.
+ new int[] { AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_GPS},
+
+ // "Personal" page.
+ new int[] { AppOpsManager.OP_READ_CONTACTS, AppOpsManager.OP_WRITE_CONTACTS,
+ AppOpsManager.OP_READ_CALL_LOG, AppOpsManager.OP_WRITE_CALL_LOG },
+
+ // "Device" page.
+ new int[] { AppOpsManager.OP_VIBRATE },
+ };
+
+ int mCurPos;
+
+ class MyPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener {
+
+ public MyPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return new AppOpsCategory(sPageOps[position]);
+ }
+
+ @Override
+ public int getCount() {
+ return sPageOps.length;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mPageNames[position];
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ mCurPos = position;
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ if (state == ViewPager.SCROLL_STATE_IDLE) {
+ //updateCurrentTab(mCurPos);
+ }
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // initialize the inflater
+ mInflater = inflater;
+
+ View rootView = mInflater.inflate(R.layout.app_ops_summary,
+ container, false);
+ mContentContainer = container;
+ mRootView = rootView;
+
+ mPageNames = getResources().getTextArray(R.array.app_ops_categories);
+
+ mViewPager = (ViewPager) rootView.findViewById(R.id.pager);
+ MyPagerAdapter adapter = new MyPagerAdapter(getChildFragmentManager());
+ mViewPager.setAdapter(adapter);
+ mViewPager.setOnPageChangeListener(adapter);
+ PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs);
+ tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light);
+
+ // We have to do this now because PreferenceFrameLayout looks at it
+ // only when the view is added.
+ if (container instanceof PreferenceFrameLayout) {
+ ((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true;
+ }
+
+ return rootView;
+ }
+}