Home | History | Annotate | Download | only in applications
      1 /**
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy
      6  * of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations
     14  * under the License.
     15  */
     16 
     17 package com.android.settings.applications;
     18 
     19 import android.app.ListFragment;
     20 import android.app.LoaderManager;
     21 import android.content.AsyncTaskLoader;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.Loader;
     27 import android.content.pm.ActivityInfo;
     28 import android.content.res.Configuration;
     29 import android.content.res.Resources;
     30 import android.os.Bundle;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.BaseAdapter;
     35 import android.widget.ImageView;
     36 import android.widget.ListView;
     37 import android.widget.TextView;
     38 
     39 import java.util.List;
     40 
     41 import com.android.settings.R;
     42 import com.android.settings.SettingsActivity;
     43 import com.android.settings.applications.AppOpsState.AppOpEntry;
     44 
     45 public class AppOpsCategory extends ListFragment implements
     46         LoaderManager.LoaderCallbacks<List<AppOpEntry>> {
     47 
     48     private static final int RESULT_APP_DETAILS = 1;
     49 
     50     AppOpsState mState;
     51 
     52     // This is the Adapter being used to display the list's data.
     53     AppListAdapter mAdapter;
     54 
     55     String mCurrentPkgName;
     56 
     57     public AppOpsCategory() {
     58     }
     59 
     60     public AppOpsCategory(AppOpsState.OpsTemplate template) {
     61         Bundle args = new Bundle();
     62         args.putParcelable("template", template);
     63         setArguments(args);
     64     }
     65 
     66     /**
     67      * Helper for determining if the configuration has changed in an interesting
     68      * way so we need to rebuild the app list.
     69      */
     70     public static class InterestingConfigChanges {
     71         final Configuration mLastConfiguration = new Configuration();
     72         int mLastDensity;
     73 
     74         boolean applyNewConfig(Resources res) {
     75             int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
     76             boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
     77             if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
     78                     |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
     79                 mLastDensity = res.getDisplayMetrics().densityDpi;
     80                 return true;
     81             }
     82             return false;
     83         }
     84     }
     85 
     86     /**
     87      * Helper class to look for interesting changes to the installed apps
     88      * so that the loader can be updated.
     89      */
     90     public static class PackageIntentReceiver extends BroadcastReceiver {
     91         final AppListLoader mLoader;
     92 
     93         public PackageIntentReceiver(AppListLoader loader) {
     94             mLoader = loader;
     95             IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
     96             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
     97             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
     98             filter.addDataScheme("package");
     99             mLoader.getContext().registerReceiver(this, filter);
    100             // Register for events related to sdcard installation.
    101             IntentFilter sdFilter = new IntentFilter();
    102             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
    103             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
    104             mLoader.getContext().registerReceiver(this, sdFilter);
    105         }
    106 
    107         @Override public void onReceive(Context context, Intent intent) {
    108             // Tell the loader about the change.
    109             mLoader.onContentChanged();
    110         }
    111     }
    112 
    113     /**
    114      * A custom Loader that loads all of the installed applications.
    115      */
    116     public static class AppListLoader extends AsyncTaskLoader<List<AppOpEntry>> {
    117         final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
    118         final AppOpsState mState;
    119         final AppOpsState.OpsTemplate mTemplate;
    120 
    121         List<AppOpEntry> mApps;
    122         PackageIntentReceiver mPackageObserver;
    123 
    124         public AppListLoader(Context context, AppOpsState state, AppOpsState.OpsTemplate template) {
    125             super(context);
    126             mState = state;
    127             mTemplate = template;
    128         }
    129 
    130         @Override public List<AppOpEntry> loadInBackground() {
    131             return mState.buildState(mTemplate);
    132         }
    133 
    134         /**
    135          * Called when there is new data to deliver to the client.  The
    136          * super class will take care of delivering it; the implementation
    137          * here just adds a little more logic.
    138          */
    139         @Override public void deliverResult(List<AppOpEntry> apps) {
    140             if (isReset()) {
    141                 // An async query came in while the loader is stopped.  We
    142                 // don't need the result.
    143                 if (apps != null) {
    144                     onReleaseResources(apps);
    145                 }
    146             }
    147             List<AppOpEntry> oldApps = apps;
    148             mApps = apps;
    149 
    150             if (isStarted()) {
    151                 // If the Loader is currently started, we can immediately
    152                 // deliver its results.
    153                 super.deliverResult(apps);
    154             }
    155 
    156             // At this point we can release the resources associated with
    157             // 'oldApps' if needed; now that the new result is delivered we
    158             // know that it is no longer in use.
    159             if (oldApps != null) {
    160                 onReleaseResources(oldApps);
    161             }
    162         }
    163 
    164         /**
    165          * Handles a request to start the Loader.
    166          */
    167         @Override protected void onStartLoading() {
    168             // We don't monitor changed when loading is stopped, so need
    169             // to always reload at this point.
    170             onContentChanged();
    171 
    172             if (mApps != null) {
    173                 // If we currently have a result available, deliver it
    174                 // immediately.
    175                 deliverResult(mApps);
    176             }
    177 
    178             // Start watching for changes in the app data.
    179             if (mPackageObserver == null) {
    180                 mPackageObserver = new PackageIntentReceiver(this);
    181             }
    182 
    183             // Has something interesting in the configuration changed since we
    184             // last built the app list?
    185             boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
    186 
    187             if (takeContentChanged() || mApps == null || configChange) {
    188                 // If the data has changed since the last time it was loaded
    189                 // or is not currently available, start a load.
    190                 forceLoad();
    191             }
    192         }
    193 
    194         /**
    195          * Handles a request to stop the Loader.
    196          */
    197         @Override protected void onStopLoading() {
    198             // Attempt to cancel the current load task if possible.
    199             cancelLoad();
    200         }
    201 
    202         /**
    203          * Handles a request to cancel a load.
    204          */
    205         @Override public void onCanceled(List<AppOpEntry> apps) {
    206             super.onCanceled(apps);
    207 
    208             // At this point we can release the resources associated with 'apps'
    209             // if needed.
    210             onReleaseResources(apps);
    211         }
    212 
    213         /**
    214          * Handles a request to completely reset the Loader.
    215          */
    216         @Override protected void onReset() {
    217             super.onReset();
    218 
    219             // Ensure the loader is stopped
    220             onStopLoading();
    221 
    222             // At this point we can release the resources associated with 'apps'
    223             // if needed.
    224             if (mApps != null) {
    225                 onReleaseResources(mApps);
    226                 mApps = null;
    227             }
    228 
    229             // Stop monitoring for changes.
    230             if (mPackageObserver != null) {
    231                 getContext().unregisterReceiver(mPackageObserver);
    232                 mPackageObserver = null;
    233             }
    234         }
    235 
    236         /**
    237          * Helper function to take care of releasing resources associated
    238          * with an actively loaded data set.
    239          */
    240         protected void onReleaseResources(List<AppOpEntry> apps) {
    241             // For a simple List<> there is nothing to do.  For something
    242             // like a Cursor, we would close it here.
    243         }
    244     }
    245 
    246     public static class AppListAdapter extends BaseAdapter {
    247         private final Resources mResources;
    248         private final LayoutInflater mInflater;
    249         private final AppOpsState mState;
    250 
    251         List<AppOpEntry> mList;
    252 
    253         public AppListAdapter(Context context, AppOpsState state) {
    254             mResources = context.getResources();
    255             mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    256             mState = state;
    257         }
    258 
    259         public void setData(List<AppOpEntry> data) {
    260             mList = data;
    261             notifyDataSetChanged();
    262         }
    263 
    264         @Override
    265         public int getCount() {
    266             return mList != null ? mList.size() : 0;
    267         }
    268 
    269         @Override
    270         public AppOpEntry getItem(int position) {
    271             return mList.get(position);
    272         }
    273 
    274         @Override
    275         public long getItemId(int position) {
    276             return position;
    277         }
    278 
    279         /**
    280          * Populate new items in the list.
    281          */
    282         @Override public View getView(int position, View convertView, ViewGroup parent) {
    283             View view;
    284 
    285             if (convertView == null) {
    286                 view = mInflater.inflate(R.layout.app_ops_item, parent, false);
    287             } else {
    288                 view = convertView;
    289             }
    290 
    291             AppOpEntry item = getItem(position);
    292             ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(
    293                     item.getAppEntry().getIcon());
    294             ((TextView)view.findViewById(R.id.app_name)).setText(item.getAppEntry().getLabel());
    295             ((TextView)view.findViewById(R.id.op_name)).setText(item.getSummaryText(mState));
    296             ((TextView)view.findViewById(R.id.op_time)).setText(
    297                     item.getTimeText(mResources, false));
    298 
    299             return view;
    300         }
    301     }
    302 
    303     @Override
    304     public void onCreate(Bundle savedInstanceState) {
    305         super.onCreate(savedInstanceState);
    306         mState = new AppOpsState(getActivity());
    307     }
    308 
    309     @Override public void onActivityCreated(Bundle savedInstanceState) {
    310         super.onActivityCreated(savedInstanceState);
    311 
    312         // Give some text to display if there is no data.  In a real
    313         // application this would come from a resource.
    314         setEmptyText("No applications");
    315 
    316         // We have a menu item to show in action bar.
    317         setHasOptionsMenu(true);
    318 
    319         // Create an empty adapter we will use to display the loaded data.
    320         mAdapter = new AppListAdapter(getActivity(), mState);
    321         setListAdapter(mAdapter);
    322 
    323         // Start out with a progress indicator.
    324         setListShown(false);
    325 
    326         // Prepare the loader.
    327         getLoaderManager().initLoader(0, null, this);
    328     }
    329 
    330     // utility method used to start sub activity
    331     private void startApplicationDetailsActivity() {
    332         // start new fragment to display extended information
    333         Bundle args = new Bundle();
    334         args.putString(AppOpsDetails.ARG_PACKAGE_NAME, mCurrentPkgName);
    335 
    336         SettingsActivity sa = (SettingsActivity) getActivity();
    337         sa.startPreferencePanel(AppOpsDetails.class.getName(), args,
    338                 R.string.app_ops_settings, null, this, RESULT_APP_DETAILS);
    339     }
    340 
    341     @Override public void onListItemClick(ListView l, View v, int position, long id) {
    342         AppOpEntry entry = mAdapter.getItem(position);
    343         if (entry != null) {
    344             mCurrentPkgName = entry.getAppEntry().getApplicationInfo().packageName;
    345             startApplicationDetailsActivity();
    346         }
    347     }
    348 
    349     @Override public Loader<List<AppOpEntry>> onCreateLoader(int id, Bundle args) {
    350         Bundle fargs = getArguments();
    351         AppOpsState.OpsTemplate template = null;
    352         if (fargs != null) {
    353             template = (AppOpsState.OpsTemplate)fargs.getParcelable("template");
    354         }
    355         return new AppListLoader(getActivity(), mState, template);
    356     }
    357 
    358     @Override public void onLoadFinished(Loader<List<AppOpEntry>> loader, List<AppOpEntry> data) {
    359         // Set the new data in the adapter.
    360         mAdapter.setData(data);
    361 
    362         // The list should now be shown.
    363         if (isResumed()) {
    364             setListShown(true);
    365         } else {
    366             setListShownNoAnimation(true);
    367         }
    368     }
    369 
    370     @Override public void onLoaderReset(Loader<List<AppOpEntry>> loader) {
    371         // Clear the data in the adapter.
    372         mAdapter.setData(null);
    373     }
    374 }
    375