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.UserHandle; 29 import android.telephony.SubscriptionManager; 30 import android.util.ArraySet; 31 import android.util.IconDrawableFactory; 32 import android.util.Log; 33 import android.view.View; 34 import android.widget.AdapterView; 35 36 import androidx.annotation.VisibleForTesting; 37 import androidx.loader.app.LoaderManager; 38 import androidx.loader.content.Loader; 39 import androidx.preference.Preference; 40 import androidx.preference.Preference.OnPreferenceChangeListener; 41 import androidx.preference.PreferenceCategory; 42 43 import com.android.settings.R; 44 import com.android.settings.applications.AppInfoBase; 45 import com.android.settings.widget.EntityHeaderController; 46 import com.android.settingslib.AppItem; 47 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 48 import com.android.settingslib.RestrictedLockUtilsInternal; 49 import com.android.settingslib.RestrictedSwitchPreference; 50 import com.android.settingslib.net.NetworkCycleDataForUid; 51 import com.android.settingslib.net.NetworkCycleDataForUidLoader; 52 import com.android.settingslib.net.UidDetail; 53 import com.android.settingslib.net.UidDetailProvider; 54 55 import java.util.ArrayList; 56 import java.util.List; 57 58 public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceChangeListener, 59 DataSaverBackend.Listener { 60 61 private static final String TAG = "AppDataUsage"; 62 63 static final String ARG_APP_ITEM = "app_item"; 64 static final String ARG_NETWORK_TEMPLATE = "network_template"; 65 static final String ARG_NETWORK_CYCLES = "network_cycles"; 66 static final String ARG_SELECTED_CYCLE = "selected_cycle"; 67 68 private static final String KEY_TOTAL_USAGE = "total_usage"; 69 private static final String KEY_FOREGROUND_USAGE = "foreground_usage"; 70 private static final String KEY_BACKGROUND_USAGE = "background_usage"; 71 private static final String KEY_APP_SETTINGS = "app_settings"; 72 private static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; 73 private static final String KEY_APP_LIST = "app_list"; 74 private static final String KEY_CYCLE = "cycle"; 75 private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver"; 76 77 private static final int LOADER_APP_USAGE_DATA = 2; 78 private static final int LOADER_APP_PREF = 3; 79 80 private PackageManager mPackageManager; 81 private final ArraySet<String> mPackages = new ArraySet<>(); 82 private Preference mTotalUsage; 83 private Preference mForegroundUsage; 84 private Preference mBackgroundUsage; 85 private Preference mAppSettings; 86 private RestrictedSwitchPreference mRestrictBackground; 87 private PreferenceCategory mAppList; 88 89 private Drawable mIcon; 90 @VisibleForTesting 91 CharSequence mLabel; 92 @VisibleForTesting 93 String mPackageName; 94 private CycleAdapter mCycleAdapter; 95 96 private List<NetworkCycleDataForUid> mUsageData; 97 @VisibleForTesting 98 NetworkTemplate mTemplate; 99 private AppItem mAppItem; 100 private Intent mAppSettingsIntent; 101 private SpinnerPreference mCycle; 102 private RestrictedSwitchPreference mUnrestrictedData; 103 private DataSaverBackend mDataSaverBackend; 104 private Context mContext; 105 private ArrayList<Long> mCycles; 106 private long mSelectedCycle; 107 108 @Override onCreate(Bundle icicle)109 public void onCreate(Bundle icicle) { 110 super.onCreate(icicle); 111 mContext = getContext(); 112 mPackageManager = getPackageManager(); 113 final Bundle args = getArguments(); 114 115 mAppItem = (args != null) ? (AppItem) args.getParcelable(ARG_APP_ITEM) : null; 116 mTemplate = (args != null) ? (NetworkTemplate) args.getParcelable(ARG_NETWORK_TEMPLATE) 117 : null; 118 mCycles = (args != null) ? (ArrayList) args.getSerializable(ARG_NETWORK_CYCLES) 119 : null; 120 mSelectedCycle = (args != null) ? args.getLong(ARG_SELECTED_CYCLE) : 0L; 121 122 if (mTemplate == null) { 123 mTemplate = DataUsageUtils.getDefaultTemplate(mContext, 124 SubscriptionManager.getDefaultDataSubscriptionId()); 125 } 126 if (mAppItem == null) { 127 int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1) 128 : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); 129 if (uid == -1) { 130 // TODO: Log error. 131 getActivity().finish(); 132 } else { 133 addUid(uid); 134 mAppItem = new AppItem(uid); 135 mAppItem.addUid(uid); 136 } 137 } else { 138 for (int i = 0; i < mAppItem.uids.size(); i++) { 139 addUid(mAppItem.uids.keyAt(i)); 140 } 141 } 142 143 mTotalUsage = findPreference(KEY_TOTAL_USAGE); 144 mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); 145 mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); 146 147 mCycle = findPreference(KEY_CYCLE); 148 mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); 149 150 final UidDetailProvider uidDetailProvider = getUidDetailProvider(); 151 152 if (mAppItem.key > 0) { 153 if (!UserHandle.isApp(mAppItem.key)) { 154 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); 155 mIcon = uidDetail.icon; 156 mLabel = uidDetail.label; 157 removePreference(KEY_UNRESTRICTED_DATA); 158 removePreference(KEY_RESTRICT_BACKGROUND); 159 } else { 160 if (mPackages.size() != 0) { 161 try { 162 final ApplicationInfo info = mPackageManager.getApplicationInfoAsUser( 163 mPackages.valueAt(0), 0, UserHandle.getUserId(mAppItem.key)); 164 mIcon = IconDrawableFactory.newInstance(getActivity()).getBadgedIcon(info); 165 mLabel = info.loadLabel(mPackageManager); 166 mPackageName = info.packageName; 167 } catch (PackageManager.NameNotFoundException e) { 168 } 169 } 170 mRestrictBackground = findPreference(KEY_RESTRICT_BACKGROUND); 171 mRestrictBackground.setOnPreferenceChangeListener(this); 172 mUnrestrictedData = findPreference(KEY_UNRESTRICTED_DATA); 173 mUnrestrictedData.setOnPreferenceChangeListener(this); 174 } 175 mDataSaverBackend = new DataSaverBackend(mContext); 176 mAppSettings = findPreference(KEY_APP_SETTINGS); 177 178 mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); 179 mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); 180 181 final PackageManager pm = getPackageManager(); 182 boolean matchFound = false; 183 for (String packageName : mPackages) { 184 mAppSettingsIntent.setPackage(packageName); 185 if (pm.resolveActivity(mAppSettingsIntent, 0) != null) { 186 matchFound = true; 187 break; 188 } 189 } 190 if (!matchFound) { 191 removePreference(KEY_APP_SETTINGS); 192 mAppSettings = null; 193 } 194 195 if (mPackages.size() > 1) { 196 mAppList = findPreference(KEY_APP_LIST); 197 LoaderManager.getInstance(this).restartLoader(LOADER_APP_PREF, Bundle.EMPTY, 198 mAppPrefCallbacks); 199 } else { 200 removePreference(KEY_APP_LIST); 201 } 202 } else { 203 final Context context = getActivity(); 204 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); 205 mIcon = uidDetail.icon; 206 mLabel = uidDetail.label; 207 mPackageName = context.getPackageName(); 208 209 removePreference(KEY_UNRESTRICTED_DATA); 210 removePreference(KEY_APP_SETTINGS); 211 removePreference(KEY_RESTRICT_BACKGROUND); 212 removePreference(KEY_APP_LIST); 213 } 214 } 215 216 @Override onResume()217 public void onResume() { 218 super.onResume(); 219 if (mDataSaverBackend != null) { 220 mDataSaverBackend.addListener(this); 221 } 222 LoaderManager.getInstance(this).restartLoader(LOADER_APP_USAGE_DATA, null /* args */, 223 mUidDataCallbacks); 224 updatePrefs(); 225 } 226 227 @Override onPause()228 public void onPause() { 229 super.onPause(); 230 if (mDataSaverBackend != null) { 231 mDataSaverBackend.remListener(this); 232 } 233 } 234 235 @Override onPreferenceChange(Preference preference, Object newValue)236 public boolean onPreferenceChange(Preference preference, Object newValue) { 237 if (preference == mRestrictBackground) { 238 mDataSaverBackend.setIsDenylisted(mAppItem.key, mPackageName, !(Boolean) newValue); 239 updatePrefs(); 240 return true; 241 } else if (preference == mUnrestrictedData) { 242 mDataSaverBackend.setIsAllowlisted(mAppItem.key, mPackageName, (Boolean) newValue); 243 return true; 244 } 245 return false; 246 } 247 248 @Override onPreferenceTreeClick(Preference preference)249 public boolean onPreferenceTreeClick(Preference preference) { 250 if (preference == mAppSettings) { 251 // TODO: target towards entire UID instead of just first package 252 getActivity().startActivityAsUser(mAppSettingsIntent, new UserHandle( 253 UserHandle.getUserId(mAppItem.key))); 254 return true; 255 } 256 return super.onPreferenceTreeClick(preference); 257 } 258 259 @Override getPreferenceScreenResId()260 protected int getPreferenceScreenResId() { 261 return R.xml.app_data_usage; 262 } 263 264 @Override getLogTag()265 protected String getLogTag() { 266 return TAG; 267 } 268 269 @VisibleForTesting updatePrefs()270 void updatePrefs() { 271 updatePrefs(getAppRestrictBackground(), getUnrestrictData()); 272 } 273 274 @VisibleForTesting getUidDetailProvider()275 UidDetailProvider getUidDetailProvider() { 276 return new UidDetailProvider(mContext); 277 } 278 updatePrefs(boolean restrictBackground, boolean unrestrictData)279 private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { 280 final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( 281 mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); 282 if (mRestrictBackground != null) { 283 mRestrictBackground.setChecked(!restrictBackground); 284 mRestrictBackground.setDisabledByAdmin(admin); 285 } 286 if (mUnrestrictedData != null) { 287 if (restrictBackground) { 288 mUnrestrictedData.setVisible(false); 289 } else { 290 mUnrestrictedData.setVisible(true); 291 mUnrestrictedData.setChecked(unrestrictData); 292 mUnrestrictedData.setDisabledByAdmin(admin); 293 } 294 } 295 } 296 addUid(int uid)297 private void addUid(int uid) { 298 String[] packages = mPackageManager.getPackagesForUid(uid); 299 if (packages != null) { 300 for (int i = 0; i < packages.length; i++) { 301 mPackages.add(packages[i]); 302 } 303 } 304 } 305 306 @VisibleForTesting bindData(int position)307 void bindData(int position) { 308 final long backgroundBytes, foregroundBytes; 309 if (mUsageData == null || position >= mUsageData.size()) { 310 backgroundBytes = foregroundBytes = 0; 311 mCycle.setVisible(false); 312 } else { 313 mCycle.setVisible(true); 314 final NetworkCycleDataForUid data = mUsageData.get(position); 315 backgroundBytes = data.getBackgroudUsage(); 316 foregroundBytes = data.getForegroudUsage(); 317 } 318 final long totalBytes = backgroundBytes + foregroundBytes; 319 320 mTotalUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, totalBytes)); 321 mForegroundUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, foregroundBytes)); 322 mBackgroundUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, backgroundBytes)); 323 } 324 getAppRestrictBackground()325 private boolean getAppRestrictBackground() { 326 final int uid = mAppItem.key; 327 final int uidPolicy = services.mPolicyManager.getUidPolicy(uid); 328 return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; 329 } 330 getUnrestrictData()331 private boolean getUnrestrictData() { 332 if (mDataSaverBackend != null) { 333 return mDataSaverBackend.isAllowlisted(mAppItem.key); 334 } 335 return false; 336 } 337 338 @Override onViewCreated(View view, Bundle savedInstanceState)339 public void onViewCreated(View view, Bundle savedInstanceState) { 340 super.onViewCreated(view, savedInstanceState); 341 342 String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; 343 int uid = 0; 344 if (pkg != null) { 345 try { 346 uid = mPackageManager.getPackageUidAsUser(pkg, 347 UserHandle.getUserId(mAppItem.key)); 348 } catch (PackageManager.NameNotFoundException e) { 349 Log.w(TAG, "Skipping UID because cannot find package " + pkg); 350 } 351 } 352 353 final boolean showInfoButton = mAppItem.key > 0; 354 355 final Activity activity = getActivity(); 356 final Preference pref = EntityHeaderController 357 .newInstance(activity, this, null /* header */) 358 .setRecyclerView(getListView(), getSettingsLifecycle()) 359 .setUid(uid) 360 .setHasAppInfoLink(showInfoButton) 361 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, 362 EntityHeaderController.ActionType.ACTION_NONE) 363 .setIcon(mIcon) 364 .setLabel(mLabel) 365 .setPackageName(pkg) 366 .done(activity, getPrefContext()); 367 getPreferenceScreen().addPreference(pref); 368 } 369 370 @Override getMetricsCategory()371 public int getMetricsCategory() { 372 return SettingsEnums.APP_DATA_USAGE; 373 } 374 375 private AdapterView.OnItemSelectedListener mCycleListener = 376 new AdapterView.OnItemSelectedListener() { 377 @Override 378 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 379 bindData(position); 380 } 381 382 @Override 383 public void onNothingSelected(AdapterView<?> parent) { 384 // ignored 385 } 386 }; 387 388 @VisibleForTesting 389 final LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>> mUidDataCallbacks = 390 new LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>>() { 391 @Override 392 public Loader<List<NetworkCycleDataForUid>> onCreateLoader(int id, Bundle args) { 393 final NetworkCycleDataForUidLoader.Builder builder 394 = NetworkCycleDataForUidLoader.builder(mContext); 395 builder.setRetrieveDetail(true) 396 .setNetworkTemplate(mTemplate); 397 if (mAppItem.category == AppItem.CATEGORY_USER) { 398 for (int i = 0; i < mAppItem.uids.size(); i++) { 399 builder.addUid(mAppItem.uids.keyAt(i)); 400 } 401 } else { 402 builder.addUid(mAppItem.key); 403 } 404 if (mCycles != null) { 405 builder.setCycles(mCycles); 406 } 407 return builder.build(); 408 } 409 410 @Override 411 public void onLoadFinished(Loader<List<NetworkCycleDataForUid>> loader, 412 List<NetworkCycleDataForUid> data) { 413 mUsageData = data; 414 mCycleAdapter.updateCycleList(data); 415 if (mSelectedCycle > 0L) { 416 final int numCycles = data.size(); 417 int position = 0; 418 for (int i = 0; i < numCycles; i++) { 419 final NetworkCycleDataForUid cycleData = data.get(i); 420 if (cycleData.getEndTime() == mSelectedCycle) { 421 position = i; 422 break; 423 } 424 } 425 if (position > 0) { 426 mCycle.setSelection(position); 427 } 428 bindData(position); 429 } else { 430 bindData(0 /* position */); 431 } 432 } 433 434 @Override 435 public void onLoaderReset(Loader<List<NetworkCycleDataForUid>> loader) { 436 } 437 }; 438 439 private final LoaderManager.LoaderCallbacks<ArraySet<Preference>> mAppPrefCallbacks = 440 new LoaderManager.LoaderCallbacks<ArraySet<Preference>>() { 441 @Override 442 public Loader<ArraySet<Preference>> onCreateLoader(int i, Bundle bundle) { 443 return new AppPrefLoader(getPrefContext(), mPackages, getPackageManager()); 444 } 445 446 @Override 447 public void onLoadFinished(Loader<ArraySet<Preference>> loader, 448 ArraySet<Preference> preferences) { 449 if (preferences != null && mAppList != null) { 450 for (Preference preference : preferences) { 451 mAppList.addPreference(preference); 452 } 453 } 454 } 455 456 @Override 457 public void onLoaderReset(Loader<ArraySet<Preference>> loader) { 458 } 459 }; 460 461 @Override onDataSaverChanged(boolean isDataSaving)462 public void onDataSaverChanged(boolean isDataSaving) { 463 464 } 465 466 @Override onAllowlistStatusChanged(int uid, boolean isAllowlisted)467 public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) { 468 if (mAppItem.uids.get(uid, false)) { 469 updatePrefs(getAppRestrictBackground(), isAllowlisted); 470 } 471 } 472 473 @Override onDenylistStatusChanged(int uid, boolean isDenylisted)474 public void onDenylistStatusChanged(int uid, boolean isDenylisted) { 475 if (mAppItem.uids.get(uid, false)) { 476 updatePrefs(isDenylisted, getUnrestrictData()); 477 } 478 } 479 } 480