blob: 03ebb9e5e08858381777f2689f728e9cfa572e60 [file] [log] [blame]
/**
* Copyright (C) 2013 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.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.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.applications.AppOpsState.AppOpEntry;
public class AppOpsCategory extends ListFragment implements
LoaderManager.LoaderCallbacks<List<AppOpEntry>> {
private static final int RESULT_APP_DETAILS = 1;
AppOpsState mState;
// This is the Adapter being used to display the list's data.
AppListAdapter mAdapter;
String mCurrentPkgName;
public AppOpsCategory() {
}
public AppOpsCategory(AppOpsState.OpsTemplate template) {
Bundle args = new Bundle();
args.putParcelable("template", template);
setArguments(args);
}
/**
* 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 AppOpsState mState;
final AppOpsState.OpsTemplate mTemplate;
List<AppOpEntry> mApps;
PackageIntentReceiver mPackageObserver;
public AppListLoader(Context context, AppOpsState state, AppOpsState.OpsTemplate template) {
super(context);
mState = state;
mTemplate = template;
}
@Override public List<AppOpEntry> loadInBackground() {
return mState.buildState(mTemplate);
}
/**
* 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() {
// We don't monitor changed when loading is stopped, so need
// to always reload at this point.
onContentChanged();
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 BaseAdapter {
private final Resources mResources;
private final LayoutInflater mInflater;
private final AppOpsState mState;
List<AppOpEntry> mList;
public AppListAdapter(Context context, AppOpsState state) {
mResources = context.getResources();
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mState = state;
}
public void setData(List<AppOpEntry> data) {
mList = data;
notifyDataSetChanged();
}
@Override
public int getCount() {
return mList != null ? mList.size() : 0;
}
@Override
public AppOpEntry getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 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(item.getSummaryText(mState));
((TextView)view.findViewById(R.id.op_time)).setText(
item.getTimeText(mResources, false));
return view;
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mState = new AppOpsState(getActivity());
}
@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(), mState);
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader.
getLoaderManager().initLoader(0, null, this);
}
// utility method used to start sub activity
private void startApplicationDetailsActivity() {
// start new fragment to display extended information
Bundle args = new Bundle();
args.putString(AppOpsDetails.ARG_PACKAGE_NAME, mCurrentPkgName);
SettingsActivity sa = (SettingsActivity) getActivity();
sa.startPreferencePanel(AppOpsDetails.class.getName(), args,
R.string.app_ops_settings, null, this, RESULT_APP_DETAILS);
}
@Override public void onListItemClick(ListView l, View v, int position, long id) {
AppOpEntry entry = mAdapter.getItem(position);
if (entry != null) {
mCurrentPkgName = entry.getAppEntry().getApplicationInfo().packageName;
startApplicationDetailsActivity();
}
}
@Override public Loader<List<AppOpEntry>> onCreateLoader(int id, Bundle args) {
Bundle fargs = getArguments();
AppOpsState.OpsTemplate template = null;
if (fargs != null) {
template = (AppOpsState.OpsTemplate)fargs.getParcelable("template");
}
return new AppListLoader(getActivity(), mState, template);
}
@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);
}
}