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