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 AppOpsCategory()57 public AppOpsCategory() { 58 } 59 AppOpsCategory(AppOpsState.OpsTemplate template)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 applyNewConfig(Resources res)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 PackageIntentReceiver(AppListLoader loader)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 onReceive(Context context, Intent intent)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 AppListLoader(Context context, AppOpsState state, AppOpsState.OpsTemplate template)124 public AppListLoader(Context context, AppOpsState state, AppOpsState.OpsTemplate template) { 125 super(context); 126 mState = state; 127 mTemplate = template; 128 } 129 loadInBackground()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 */ deliverResult(List<AppOpEntry> apps)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 */ onStartLoading()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 */ onStopLoading()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 */ onCanceled(List<AppOpEntry> apps)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 */ onReset()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 */ onReleaseResources(List<AppOpEntry> apps)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 AppListAdapter(Context context, AppOpsState state)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 setData(List<AppOpEntry> data)259 public void setData(List<AppOpEntry> data) { 260 mList = data; 261 notifyDataSetChanged(); 262 } 263 264 @Override getCount()265 public int getCount() { 266 return mList != null ? mList.size() : 0; 267 } 268 269 @Override getItem(int position)270 public AppOpEntry getItem(int position) { 271 return mList.get(position); 272 } 273 274 @Override getItemId(int position)275 public long getItemId(int position) { 276 return position; 277 } 278 279 /** 280 * Populate new items in the list. 281 */ getView(int position, View convertView, ViewGroup parent)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 onCreate(Bundle savedInstanceState)304 public void onCreate(Bundle savedInstanceState) { 305 super.onCreate(savedInstanceState); 306 mState = new AppOpsState(getActivity()); 307 } 308 onActivityCreated(Bundle savedInstanceState)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 startApplicationDetailsActivity()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 onListItemClick(ListView l, View v, int position, long id)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 onCreateLoader(int id, Bundle args)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 onLoadFinished(Loader<List<AppOpEntry>> loader, List<AppOpEntry> data)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 onLoaderReset(Loader<List<AppOpEntry>> loader)370 @Override public void onLoaderReset(Loader<List<AppOpEntry>> loader) { 371 // Clear the data in the adapter. 372 mAdapter.setData(null); 373 } 374 } 375