1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.settings.datausage; 16 17 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 18 19 import android.app.Activity; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.graphics.drawable.Drawable; 26 import android.net.NetworkTemplate; 27 import android.os.Bundle; 28 import android.os.Process; 29 import android.os.UserHandle; 30 import android.telephony.SubscriptionManager; 31 import android.util.ArraySet; 32 import android.util.IconDrawableFactory; 33 import android.util.Log; 34 import android.view.View; 35 import android.widget.AdapterView; 36 37 import androidx.annotation.VisibleForTesting; 38 import androidx.loader.app.LoaderManager; 39 import androidx.loader.content.Loader; 40 import androidx.preference.Preference; 41 import androidx.preference.Preference.OnPreferenceChangeListener; 42 import androidx.preference.PreferenceCategory; 43 import androidx.recyclerview.widget.DefaultItemAnimator; 44 import androidx.recyclerview.widget.RecyclerView; 45 46 import com.android.settings.R; 47 import com.android.settings.applications.AppInfoBase; 48 import com.android.settings.network.SubscriptionUtil; 49 import com.android.settings.widget.EntityHeaderController; 50 import com.android.settingslib.AppItem; 51 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 52 import com.android.settingslib.RestrictedLockUtilsInternal; 53 import com.android.settingslib.RestrictedSwitchPreference; 54 import com.android.settingslib.net.NetworkCycleDataForUid; 55 import com.android.settingslib.net.NetworkCycleDataForUidLoader; 56 import com.android.settingslib.net.UidDetail; 57 import com.android.settingslib.net.UidDetailProvider; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 62 public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceChangeListener, 63 DataSaverBackend.Listener { 64 65 private static final String TAG = "AppDataUsage"; 66 67 static final String ARG_APP_ITEM = "app_item"; 68 static final String ARG_NETWORK_TEMPLATE = "network_template"; 69 static final String ARG_NETWORK_CYCLES = "network_cycles"; 70 static final String ARG_SELECTED_CYCLE = "selected_cycle"; 71 72 private static final String KEY_TOTAL_USAGE = "total_usage"; 73 private static final String KEY_FOREGROUND_USAGE = "foreground_usage"; 74 private static final String KEY_BACKGROUND_USAGE = "background_usage"; 75 private static final String KEY_APP_SETTINGS = "app_settings"; 76 private static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; 77 private static final String KEY_APP_LIST = "app_list"; 78 private static final String KEY_CYCLE = "cycle"; 79 private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver"; 80 81 private static final int LOADER_APP_USAGE_DATA = 2; 82 private static final int LOADER_APP_PREF = 3; 83 84 private PackageManager mPackageManager; 85 private final ArraySet<String> mPackages = new ArraySet<>(); 86 private Preference mTotalUsage; 87 private Preference mForegroundUsage; 88 private Preference mBackgroundUsage; 89 private Preference mAppSettings; 90 private RestrictedSwitchPreference mRestrictBackground; 91 private PreferenceCategory mAppList; 92 93 private Drawable mIcon; 94 @VisibleForTesting 95 CharSequence mLabel; 96 @VisibleForTesting 97 String mPackageName; 98 private CycleAdapter mCycleAdapter; 99 100 private List<NetworkCycleDataForUid> mUsageData; 101 @VisibleForTesting 102 NetworkTemplate mTemplate; 103 private AppItem mAppItem; 104 private Intent mAppSettingsIntent; 105 private SpinnerPreference mCycle; 106 private RestrictedSwitchPreference mUnrestrictedData; 107 private DataSaverBackend mDataSaverBackend; 108 private Context mContext; 109 private ArrayList<Long> mCycles; 110 private long mSelectedCycle; 111 private boolean mIsLoading; 112 isSimHardwareVisible(Context context)113 public boolean isSimHardwareVisible(Context context) { 114 return SubscriptionUtil.isSimHardwareVisible(context); 115 } 116 117 @Override onCreate(Bundle icicle)118 public void onCreate(Bundle icicle) { 119 super.onCreate(icicle); 120 mContext = getContext(); 121 mPackageManager = getPackageManager(); 122 final Bundle args = getArguments(); 123 124 mAppItem = (args != null) ? (AppItem) args.getParcelable(ARG_APP_ITEM) : null; 125 mTemplate = (args != null) ? (NetworkTemplate) args.getParcelable(ARG_NETWORK_TEMPLATE) 126 : null; 127 mCycles = (args != null) ? (ArrayList) args.getSerializable(ARG_NETWORK_CYCLES) 128 : null; 129 mSelectedCycle = (args != null) ? args.getLong(ARG_SELECTED_CYCLE) : 0L; 130 131 if (mTemplate == null) { 132 mTemplate = DataUsageUtils.getDefaultTemplate(mContext, 133 SubscriptionManager.getDefaultDataSubscriptionId()); 134 } 135 if (mAppItem == null) { 136 int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1) 137 : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); 138 if (uid == -1) { 139 // TODO: Log error. 140 getActivity().finish(); 141 } else { 142 addUid(uid); 143 mAppItem = new AppItem(uid); 144 mAppItem.addUid(uid); 145 } 146 } else { 147 for (int i = 0; i < mAppItem.uids.size(); i++) { 148 addUid(mAppItem.uids.keyAt(i)); 149 } 150 } 151 152 if (mAppItem.key > 0 && UserHandle.isApp(mAppItem.key)) { 153 // In case we've been asked data usage for an app, automatically 154 // include data usage of the corresponding SDK sandbox 155 final int appSandboxUid = Process.toSdkSandboxUid(mAppItem.key); 156 if (!mAppItem.uids.get(appSandboxUid)) { 157 mAppItem.addUid(appSandboxUid); 158 } 159 } 160 mTotalUsage = findPreference(KEY_TOTAL_USAGE); 161 mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); 162 mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); 163 164 initCycle(); 165 166 final UidDetailProvider uidDetailProvider = getUidDetailProvider(); 167 168 if (mAppItem.key > 0) { 169 if ((!isSimHardwareVisible(mContext)) || !UserHandle.isApp(mAppItem.key)) { 170 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); 171 mIcon = uidDetail.icon; 172 mLabel = uidDetail.label; 173 removePreference(KEY_UNRESTRICTED_DATA); 174 removePreference(KEY_RESTRICT_BACKGROUND); 175 } else { 176 if (mPackages.size() != 0) { 177 try { 178 final ApplicationInfo info = mPackageManager.getApplicationInfoAsUser( 179 mPackages.valueAt(0), 0, UserHandle.getUserId(mAppItem.key)); 180 mIcon = IconDrawableFactory.newInstance(getActivity()).getBadgedIcon(info); 181 mLabel = info.loadLabel(mPackageManager); 182 mPackageName = info.packageName; 183 } catch (PackageManager.NameNotFoundException e) { 184 } 185 } 186 mRestrictBackground = findPreference(KEY_RESTRICT_BACKGROUND); 187 mRestrictBackground.setOnPreferenceChangeListener(this); 188 mUnrestrictedData = findPreference(KEY_UNRESTRICTED_DATA); 189 mUnrestrictedData.setOnPreferenceChangeListener(this); 190 } 191 mDataSaverBackend = new DataSaverBackend(mContext); 192 mAppSettings = findPreference(KEY_APP_SETTINGS); 193 194 mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); 195 mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); 196 197 final PackageManager pm = getPackageManager(); 198 boolean matchFound = false; 199 for (String packageName : mPackages) { 200 mAppSettingsIntent.setPackage(packageName); 201 if (pm.resolveActivity(mAppSettingsIntent, 0) != null) { 202 matchFound = true; 203 break; 204 } 205 } 206 if (!matchFound) { 207 removePreference(KEY_APP_SETTINGS); 208 mAppSettings = null; 209 } 210 211 if (mPackages.size() > 1) { 212 mAppList = findPreference(KEY_APP_LIST); 213 LoaderManager.getInstance(this).restartLoader(LOADER_APP_PREF, Bundle.EMPTY, 214 mAppPrefCallbacks); 215 } else { 216 removePreference(KEY_APP_LIST); 217 } 218 } else { 219 final Context context = getActivity(); 220 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); 221 mIcon = uidDetail.icon; 222 mLabel = uidDetail.label; 223 mPackageName = context.getPackageName(); 224 225 removePreference(KEY_UNRESTRICTED_DATA); 226 removePreference(KEY_APP_SETTINGS); 227 removePreference(KEY_RESTRICT_BACKGROUND); 228 removePreference(KEY_APP_LIST); 229 } 230 231 addEntityHeader(); 232 } 233 234 @Override onResume()235 public void onResume() { 236 super.onResume(); 237 // No animations will occur before: 238 // - LOADER_APP_USAGE_DATA initially updates the cycle 239 // - updatePrefs() initially updates the preference visibility 240 // This is mainly for the cycle spinner, because when the page is entered from the 241 // AppInfoDashboardFragment, there is no way to know whether the cycle data is available 242 // before finished the async loading. 243 // The animator will be set back if any page updates happens after loading, in 244 // setBackPreferenceListAnimatorIfLoaded(). 245 mIsLoading = true; 246 getListView().setItemAnimator(null); 247 if (mDataSaverBackend != null) { 248 mDataSaverBackend.addListener(this); 249 } 250 LoaderManager.getInstance(this).restartLoader(LOADER_APP_USAGE_DATA, null /* args */, 251 mUidDataCallbacks); 252 updatePrefs(); 253 } 254 255 @Override onPause()256 public void onPause() { 257 super.onPause(); 258 if (mDataSaverBackend != null) { 259 mDataSaverBackend.remListener(this); 260 } 261 } 262 263 @Override onPreferenceChange(Preference preference, Object newValue)264 public boolean onPreferenceChange(Preference preference, Object newValue) { 265 if (preference == mRestrictBackground) { 266 mDataSaverBackend.setIsDenylisted(mAppItem.key, mPackageName, !(Boolean) newValue); 267 updatePrefs(); 268 return true; 269 } else if (preference == mUnrestrictedData) { 270 mDataSaverBackend.setIsAllowlisted(mAppItem.key, mPackageName, (Boolean) newValue); 271 return true; 272 } 273 return false; 274 } 275 276 @Override onPreferenceTreeClick(Preference preference)277 public boolean onPreferenceTreeClick(Preference preference) { 278 if (preference == mAppSettings) { 279 // TODO: target towards entire UID instead of just first package 280 getActivity().startActivityAsUser(mAppSettingsIntent, new UserHandle( 281 UserHandle.getUserId(mAppItem.key))); 282 return true; 283 } 284 return super.onPreferenceTreeClick(preference); 285 } 286 287 @Override getPreferenceScreenResId()288 protected int getPreferenceScreenResId() { 289 return R.xml.app_data_usage; 290 } 291 292 @Override getLogTag()293 protected String getLogTag() { 294 return TAG; 295 } 296 297 @VisibleForTesting updatePrefs()298 void updatePrefs() { 299 updatePrefs(getAppRestrictBackground(), getUnrestrictData()); 300 } 301 302 @VisibleForTesting getUidDetailProvider()303 UidDetailProvider getUidDetailProvider() { 304 return new UidDetailProvider(mContext); 305 } 306 initCycle()307 private void initCycle() { 308 mCycle = findPreference(KEY_CYCLE); 309 mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); 310 if (mCycles != null) { 311 // If coming from a page like DataUsageList where already has a selected cycle, display 312 // that before loading to reduce flicker. 313 mCycleAdapter.setInitialCycleList(mCycles, mSelectedCycle); 314 mCycle.setHasCycles(true); 315 } 316 } 317 318 /** 319 * Sets back the preference list's animator if the loading is finished. 320 * 321 * The preference list's animator was temporarily removed before loading in onResume(). 322 * When need to update the preference visibility in this page after the loading, adding the 323 * animator back to keeping the usual animations. 324 */ setBackPreferenceListAnimatorIfLoaded()325 private void setBackPreferenceListAnimatorIfLoaded() { 326 if (mIsLoading) { 327 return; 328 } 329 RecyclerView recyclerView = getListView(); 330 if (recyclerView.getItemAnimator() == null) { 331 recyclerView.setItemAnimator(new DefaultItemAnimator()); 332 } 333 } 334 updatePrefs(boolean restrictBackground, boolean unrestrictData)335 private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { 336 if (!isSimHardwareVisible(mContext)) { 337 return; 338 } 339 setBackPreferenceListAnimatorIfLoaded(); 340 final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( 341 mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); 342 if (mRestrictBackground != null) { 343 mRestrictBackground.setChecked(!restrictBackground); 344 mRestrictBackground.setDisabledByAdmin(admin); 345 } 346 if (mUnrestrictedData != null) { 347 if (restrictBackground) { 348 mUnrestrictedData.setVisible(false); 349 } else { 350 mUnrestrictedData.setVisible(true); 351 mUnrestrictedData.setChecked(unrestrictData); 352 mUnrestrictedData.setDisabledByAdmin(admin); 353 } 354 } 355 } 356 addUid(int uid)357 private void addUid(int uid) { 358 if (Process.isSdkSandboxUid(uid)) { 359 // For a sandbox process, get the associated app UID 360 uid = Process.getAppUidForSdkSandboxUid(uid); 361 } 362 String[] packages = mPackageManager.getPackagesForUid(uid); 363 if (packages != null) { 364 for (int i = 0; i < packages.length; i++) { 365 mPackages.add(packages[i]); 366 } 367 } 368 } 369 370 @VisibleForTesting bindData(int position)371 void bindData(int position) { 372 final long backgroundBytes, foregroundBytes; 373 if (mUsageData == null || position >= mUsageData.size()) { 374 backgroundBytes = foregroundBytes = 0; 375 mCycle.setHasCycles(false); 376 } else { 377 mCycle.setHasCycles(true); 378 final NetworkCycleDataForUid data = mUsageData.get(position); 379 backgroundBytes = data.getBackgroudUsage(); 380 foregroundBytes = data.getForegroudUsage(); 381 } 382 final long totalBytes = backgroundBytes + foregroundBytes; 383 384 mTotalUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, totalBytes)); 385 mForegroundUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, foregroundBytes)); 386 mBackgroundUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, backgroundBytes)); 387 } 388 getAppRestrictBackground()389 private boolean getAppRestrictBackground() { 390 final int uid = mAppItem.key; 391 final int uidPolicy = services.mPolicyManager.getUidPolicy(uid); 392 return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; 393 } 394 getUnrestrictData()395 private boolean getUnrestrictData() { 396 if (mDataSaverBackend != null) { 397 return mDataSaverBackend.isAllowlisted(mAppItem.key); 398 } 399 return false; 400 } 401 402 @VisibleForTesting addEntityHeader()403 void addEntityHeader() { 404 String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; 405 int uid = 0; 406 if (pkg != null) { 407 try { 408 uid = mPackageManager.getPackageUidAsUser(pkg, 409 UserHandle.getUserId(mAppItem.key)); 410 } catch (PackageManager.NameNotFoundException e) { 411 Log.w(TAG, "Skipping UID because cannot find package " + pkg); 412 } 413 } 414 415 final boolean showInfoButton = mAppItem.key > 0; 416 417 final Activity activity = getActivity(); 418 final Preference pref = EntityHeaderController 419 .newInstance(activity, this, null /* header */) 420 .setRecyclerView(getListView(), getSettingsLifecycle()) 421 .setUid(uid) 422 .setHasAppInfoLink(showInfoButton) 423 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, 424 EntityHeaderController.ActionType.ACTION_NONE) 425 .setIcon(mIcon) 426 .setLabel(mLabel) 427 .setPackageName(pkg) 428 .done(activity, getPrefContext()); 429 getPreferenceScreen().addPreference(pref); 430 } 431 432 @Override getMetricsCategory()433 public int getMetricsCategory() { 434 return SettingsEnums.APP_DATA_USAGE; 435 } 436 437 private AdapterView.OnItemSelectedListener mCycleListener = 438 new AdapterView.OnItemSelectedListener() { 439 @Override 440 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 441 bindData(position); 442 } 443 444 @Override 445 public void onNothingSelected(AdapterView<?> parent) { 446 // ignored 447 } 448 }; 449 450 @VisibleForTesting 451 final LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>> mUidDataCallbacks = 452 new LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>>() { 453 @Override 454 public Loader<List<NetworkCycleDataForUid>> onCreateLoader(int id, Bundle args) { 455 final NetworkCycleDataForUidLoader.Builder builder 456 = NetworkCycleDataForUidLoader.builder(mContext); 457 builder.setRetrieveDetail(true) 458 .setNetworkTemplate(mTemplate); 459 for (int i = 0; i < mAppItem.uids.size(); i++) { 460 builder.addUid(mAppItem.uids.keyAt(i)); 461 } 462 if (mCycles != null) { 463 builder.setCycles(mCycles); 464 } 465 return builder.build(); 466 } 467 468 @Override 469 public void onLoadFinished(Loader<List<NetworkCycleDataForUid>> loader, 470 List<NetworkCycleDataForUid> data) { 471 mUsageData = data; 472 mCycleAdapter.updateCycleList(data); 473 if (mSelectedCycle > 0L) { 474 final int numCycles = data.size(); 475 int position = 0; 476 for (int i = 0; i < numCycles; i++) { 477 final NetworkCycleDataForUid cycleData = data.get(i); 478 if (cycleData.getEndTime() == mSelectedCycle) { 479 position = i; 480 break; 481 } 482 } 483 if (position > 0) { 484 mCycle.setSelection(position); 485 } 486 bindData(position); 487 } else { 488 bindData(0 /* position */); 489 } 490 mIsLoading = false; 491 } 492 493 @Override 494 public void onLoaderReset(Loader<List<NetworkCycleDataForUid>> loader) { 495 } 496 }; 497 498 private final LoaderManager.LoaderCallbacks<ArraySet<Preference>> mAppPrefCallbacks = 499 new LoaderManager.LoaderCallbacks<ArraySet<Preference>>() { 500 @Override 501 public Loader<ArraySet<Preference>> onCreateLoader(int i, Bundle bundle) { 502 return new AppPrefLoader(getPrefContext(), mPackages, getPackageManager()); 503 } 504 505 @Override 506 public void onLoadFinished(Loader<ArraySet<Preference>> loader, 507 ArraySet<Preference> preferences) { 508 if (preferences != null && mAppList != null) { 509 for (Preference preference : preferences) { 510 mAppList.addPreference(preference); 511 } 512 } 513 } 514 515 @Override 516 public void onLoaderReset(Loader<ArraySet<Preference>> loader) { 517 } 518 }; 519 520 @Override onDataSaverChanged(boolean isDataSaving)521 public void onDataSaverChanged(boolean isDataSaving) { 522 523 } 524 525 @Override onAllowlistStatusChanged(int uid, boolean isAllowlisted)526 public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) { 527 if (mAppItem.uids.get(uid, false)) { 528 updatePrefs(getAppRestrictBackground(), isAllowlisted); 529 } 530 } 531 532 @Override onDenylistStatusChanged(int uid, boolean isDenylisted)533 public void onDenylistStatusChanged(int uid, boolean isDenylisted) { 534 if (mAppItem.uids.get(uid, false)) { 535 updatePrefs(isDenylisted, getUnrestrictData()); 536 } 537 } 538 } 539