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.CompoundButton; 33 import android.widget.ImageView; 34 import android.widget.ListView; 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 248 List<AppOpEntry> mList; 249 AppListAdapter(Context context)250 public AppListAdapter(Context context) { 251 mResources = context.getResources(); 252 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 253 } 254 setData(List<AppOpEntry> data)255 public void setData(List<AppOpEntry> data) { 256 mList = data; 257 notifyDataSetChanged(); 258 } 259 260 @Override getCount()261 public int getCount() { 262 return mList != null ? mList.size() : 0; 263 } 264 265 @Override getItem(int position)266 public AppOpEntry getItem(int position) { 267 return mList.get(position); 268 } 269 270 @Override getItemId(int position)271 public long getItemId(int position) { 272 return position; 273 } 274 275 /** 276 * Populate new items in the list. 277 */ 278 @Override getView(int position, View convertView, ViewGroup parent)279 public View getView(int position, View convertView, ViewGroup parent) { 280 View view; 281 282 if (convertView == null) { 283 view = mInflater.inflate(R.layout.app_ops_item, parent, false); 284 } else { 285 view = convertView; 286 } 287 288 AppOpEntry item = getItem(position); 289 ((ImageView) view.findViewById(R.id.app_icon)).setImageDrawable( 290 item.getAppEntry().getIcon()); 291 ((TextView) view.findViewById(R.id.app_name)).setText(item.getAppEntry().getLabel()); 292 ((TextView) view.findViewById(R.id.op_name)).setText( 293 item.getTimeText(mResources, false)); 294 view.findViewById(R.id.op_time).setVisibility(View.GONE); 295 ((CompoundButton) view.findViewById(R.id.op_switch)).setChecked( 296 item.getPrimaryOpMode() == AppOpsManager.MODE_ALLOWED); 297 298 return view; 299 } 300 } 301 302 @Override onCreate(Bundle savedInstanceState)303 public void onCreate(Bundle savedInstanceState) { 304 super.onCreate(savedInstanceState); 305 mState = new AppOpsState(getActivity()); 306 } 307 onActivityCreated(Bundle savedInstanceState)308 @Override public void onActivityCreated(Bundle savedInstanceState) { 309 super.onActivityCreated(savedInstanceState); 310 311 // Give some text to display if there is no data. In a real 312 // application this would come from a resource. 313 setEmptyText("No applications"); 314 315 // We have a menu item to show in action bar. 316 setHasOptionsMenu(true); 317 318 // Create an empty adapter we will use to display the loaded data. 319 mAdapter = new AppListAdapter(getActivity()); 320 setListAdapter(mAdapter); 321 322 // Start out with a progress indicator. 323 setListShown(false); 324 325 // Prepare the loader. 326 getLoaderManager().initLoader(0, null, this); 327 } 328 onListItemClick(ListView l, View v, int position, long id)329 @Override public void onListItemClick(ListView l, View v, int position, long id) { 330 AppOpEntry entry = mAdapter.getItem(position); 331 if (entry != null) { 332 // We treat this as tapping on the check box, toggling the app op state. 333 CompoundButton sw = v.findViewById(R.id.op_switch); 334 boolean checked = !sw.isChecked(); 335 sw.setChecked(checked); 336 AppOpsManager.OpEntry op = entry.getOpEntry(0); 337 int mode = checked ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; 338 mState.getAppOpsManager().setMode(op.getOp(), 339 entry.getAppEntry().getApplicationInfo().uid, 340 entry.getAppEntry().getApplicationInfo().packageName, 341 mode); 342 entry.overridePrimaryOpMode(mode); 343 } 344 } 345 onCreateLoader(int id, Bundle args)346 @Override public Loader<List<AppOpEntry>> onCreateLoader(int id, Bundle args) { 347 Bundle fargs = getArguments(); 348 AppOpsState.OpsTemplate template = null; 349 if (fargs != null) { 350 template = fargs.getParcelable("template"); 351 } 352 return new AppListLoader(getActivity(), mState, template); 353 } 354 onLoadFinished(Loader<List<AppOpEntry>> loader, List<AppOpEntry> data)355 @Override public void onLoadFinished(Loader<List<AppOpEntry>> loader, List<AppOpEntry> data) { 356 // Set the new data in the adapter. 357 mAdapter.setData(data); 358 359 // The list should now be shown. 360 if (isResumed()) { 361 setListShown(true); 362 } else { 363 setListShownNoAnimation(true); 364 } 365 } 366 onLoaderReset(Loader<List<AppOpEntry>> loader)367 @Override public void onLoaderReset(Loader<List<AppOpEntry>> loader) { 368 // Clear the data in the adapter. 369 mAdapter.setData(null); 370 } 371 } 372