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