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 static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid; 20 import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUidList; 21 import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings; 22 23 import android.app.Activity; 24 import android.app.settings.SettingsEnums; 25 import android.content.Context; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.graphics.drawable.Drawable; 29 import android.net.NetworkTemplate; 30 import android.os.Bundle; 31 import android.os.UserHandle; 32 import android.util.ArraySet; 33 import android.util.IconDrawableFactory; 34 import android.util.Log; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.preference.Preference; 39 import androidx.preference.Preference.OnPreferenceChangeListener; 40 import androidx.recyclerview.widget.DefaultItemAnimator; 41 import androidx.recyclerview.widget.RecyclerView; 42 43 import com.android.settings.R; 44 import com.android.settings.applications.AppInfoBase; 45 import com.android.settings.datausage.lib.AppDataUsageDetailsRepository; 46 import com.android.settings.datausage.lib.NetworkTemplates; 47 import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager; 48 import com.android.settings.network.SubscriptionUtil; 49 import com.android.settings.overlay.FeatureFactory; 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.UidDetail; 55 import com.android.settingslib.net.UidDetailProvider; 56 import com.android.settingslib.widget.IntroPreference; 57 58 import kotlin.Unit; 59 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.List; 63 64 public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceChangeListener, 65 DataSaverBackend.Listener { 66 67 private static final String TAG = "AppDataUsage"; 68 69 static final String ARG_APP_ITEM = "app_item"; 70 @VisibleForTesting 71 static final String ARG_APP_HEADER = "app_header"; 72 static final String ARG_NETWORK_TEMPLATE = "network_template"; 73 static final String ARG_NETWORK_CYCLES = "network_cycles"; 74 static final String ARG_SELECTED_CYCLE = "selected_cycle"; 75 76 private static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; 77 private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver"; 78 79 private PackageManager mPackageManager; 80 private final ArraySet<String> mPackages = new ArraySet<>(); 81 private RestrictedSwitchPreference mRestrictBackground; 82 83 private Drawable mIcon; 84 @VisibleForTesting 85 CharSequence mLabel; 86 @VisibleForTesting 87 String mPackageName; 88 89 @VisibleForTesting 90 NetworkTemplate mTemplate; 91 private AppItem mAppItem; 92 private RestrictedSwitchPreference mUnrestrictedData; 93 private DataSaverBackend mDataSaverBackend; 94 private Context mContext; 95 private ArrayList<Long> mCycles; 96 private long mSelectedCycle; 97 private boolean mIsLoading; 98 isSimHardwareVisible(Context context)99 public boolean isSimHardwareVisible(Context context) { 100 return SubscriptionUtil.isSimHardwareVisible(context); 101 } 102 103 @Override onCreate(Bundle icicle)104 public void onCreate(Bundle icicle) { 105 super.onCreate(icicle); 106 mContext = getContext(); 107 mPackageManager = getPackageManager(); 108 final Bundle args = getArguments(); 109 110 mAppItem = (args != null) ? (AppItem) args.getParcelable(ARG_APP_ITEM) : null; 111 mTemplate = (args != null) ? (NetworkTemplate) args.getParcelable(ARG_NETWORK_TEMPLATE) 112 : null; 113 mCycles = (args != null) ? (ArrayList) args.getSerializable(ARG_NETWORK_CYCLES) 114 : null; 115 mSelectedCycle = (args != null) ? args.getLong(ARG_SELECTED_CYCLE) : 0L; 116 117 if (mTemplate == null) { 118 mTemplate = NetworkTemplates.INSTANCE.getDefaultTemplate(mContext); 119 } 120 final Activity activity = requireActivity(); 121 activity.setTitle(NetworkTemplates.getTitleResId(mTemplate)); 122 if (mAppItem == null) { 123 int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1) 124 : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); 125 if (uid == -1) { 126 // TODO: Log error. 127 activity.finish(); 128 } else { 129 addUid(uid); 130 mAppItem = new AppItem(uid); 131 mAppItem.addUid(uid); 132 } 133 } else { 134 for (int i = 0; i < mAppItem.uids.size(); i++) { 135 addUid(mAppItem.uids.keyAt(i)); 136 } 137 } 138 139 final List<Integer> uidList = getAppUidList(mAppItem.uids); 140 initCycle(uidList); 141 142 final UidDetailProvider uidDetailProvider = getUidDetailProvider(); 143 144 if (mAppItem.key > 0) { 145 if ((!isSimHardwareVisible(mContext)) || !UserHandle.isApp(mAppItem.key)) { 146 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); 147 mIcon = uidDetail.icon; 148 mLabel = uidDetail.label; 149 removePreference(KEY_UNRESTRICTED_DATA); 150 removePreference(KEY_RESTRICT_BACKGROUND); 151 } else { 152 if (mPackages.size() != 0) { 153 int userId = UserHandle.getUserId(mAppItem.key); 154 try { 155 final ApplicationInfo info = mPackageManager.getApplicationInfoAsUser( 156 mPackages.valueAt(0), 0, userId); 157 mIcon = IconDrawableFactory.newInstance(getActivity()).getBadgedIcon(info); 158 mLabel = info.loadLabel(mPackageManager); 159 mPackageName = info.packageName; 160 } catch (PackageManager.NameNotFoundException e) { 161 } 162 use(AppDataUsageAppSettingsController.class).init(mPackages, userId); 163 } 164 mRestrictBackground = findPreference(KEY_RESTRICT_BACKGROUND); 165 mRestrictBackground.setOnPreferenceChangeListener(this); 166 mUnrestrictedData = findPreference(KEY_UNRESTRICTED_DATA); 167 mUnrestrictedData.setOnPreferenceChangeListener(this); 168 } 169 mDataSaverBackend = new DataSaverBackend(mContext); 170 171 use(AppDataUsageListController.class).init(uidList); 172 } else { 173 final Context context = getActivity(); 174 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); 175 mIcon = uidDetail.icon; 176 mLabel = uidDetail.label; 177 mPackageName = context.getPackageName(); 178 179 removePreference(KEY_UNRESTRICTED_DATA); 180 removePreference(KEY_RESTRICT_BACKGROUND); 181 } 182 183 setupIntroPreference(); 184 } 185 186 @Override onStart()187 public void onStart() { 188 super.onStart(); 189 // No animations will occur before bindData() initially updates the cycle. 190 // This is mainly for the cycle spinner, because when the page is entered from the 191 // AppInfoDashboardFragment, there is no way to know whether the cycle data is available 192 // before finished the async loading. 193 // The animator will be set back if any page updates happens after loading, in 194 // setBackPreferenceListAnimatorIfLoaded(). 195 mIsLoading = true; 196 getListView().setItemAnimator(null); 197 } 198 199 @Override onResume()200 public void onResume() { 201 super.onResume(); 202 if (mDataSaverBackend != null) { 203 mDataSaverBackend.addListener(this); 204 } 205 updatePrefs(); 206 } 207 208 @Override onPause()209 public void onPause() { 210 super.onPause(); 211 if (mDataSaverBackend != null) { 212 mDataSaverBackend.remListener(this); 213 } 214 } 215 216 @Override onPreferenceChange(@onNull Preference preference, Object newValue)217 public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { 218 if (preference == mRestrictBackground) { 219 mDataSaverBackend.setIsDenylisted(mAppItem.key, mPackageName, !(Boolean) newValue); 220 updatePrefs(); 221 return true; 222 } else if (preference == mUnrestrictedData) { 223 mDataSaverBackend.setIsAllowlisted(mAppItem.key, mPackageName, (Boolean) newValue); 224 return true; 225 } 226 return false; 227 } 228 229 @Override getPreferenceScreenResId()230 protected int getPreferenceScreenResId() { 231 return R.xml.app_data_usage; 232 } 233 234 @Override getLogTag()235 protected String getLogTag() { 236 return TAG; 237 } 238 239 @VisibleForTesting updatePrefs()240 void updatePrefs() { 241 updatePrefs(getAppRestrictBackground(), getUnrestrictData()); 242 } 243 244 @VisibleForTesting getUidDetailProvider()245 UidDetailProvider getUidDetailProvider() { 246 return new UidDetailProvider(mContext); 247 } 248 249 @VisibleForTesting initCycle(List<Integer> uidList)250 void initCycle(List<Integer> uidList) { 251 var cycleController = use(AppDataUsageCycleController.class); 252 var summaryController = use(AppDataUsageSummaryController.class); 253 var repository = new AppDataUsageDetailsRepository(mContext, mTemplate, mCycles, uidList); 254 cycleController.init(repository, data -> { 255 mIsLoading = false; 256 summaryController.update(data); 257 return Unit.INSTANCE; 258 }); 259 if (mCycles != null) { 260 Log.d(TAG, "setInitialCycles: " + mCycles + " " + mSelectedCycle); 261 cycleController.setInitialCycles(mCycles, mSelectedCycle); 262 } 263 } 264 265 /** 266 * Sets back the preference list's animator if the loading is finished. 267 * 268 * The preference list's animator was temporarily removed before loading in onResume(). 269 * When need to update the preference visibility in this page after the loading, adding the 270 * animator back to keeping the usual animations. 271 */ setBackPreferenceListAnimatorIfLoaded()272 private void setBackPreferenceListAnimatorIfLoaded() { 273 if (mIsLoading) { 274 return; 275 } 276 RecyclerView recyclerView = getListView(); 277 if (recyclerView.getItemAnimator() == null) { 278 recyclerView.setItemAnimator(new DefaultItemAnimator()); 279 } 280 } 281 updatePrefs(boolean restrictBackground, boolean unrestrictData)282 private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { 283 if (!isSimHardwareVisible(mContext)) { 284 return; 285 } 286 setBackPreferenceListAnimatorIfLoaded(); 287 final EnforcedAdmin admin = RestrictedLockUtilsInternal 288 .checkIfMeteredDataUsageUserControlDisabled(mContext, mPackageName, 289 UserHandle.getUserId(mAppItem.key)); 290 if (mRestrictBackground != null) { 291 mRestrictBackground.setChecked(!restrictBackground); 292 mRestrictBackground.setDisabledByAdmin(admin); 293 } 294 if (mUnrestrictedData != null) { 295 if (restrictBackground) { 296 mUnrestrictedData.setVisible(false); 297 } else { 298 mUnrestrictedData.setVisible(true); 299 mUnrestrictedData.setChecked(unrestrictData); 300 mUnrestrictedData.setDisabledByAdmin(admin); 301 } 302 } 303 } 304 addUid(int uid)305 private void addUid(int uid) { 306 String[] packages = mPackageManager.getPackagesForUid(getAppUid(uid)); 307 if (packages != null) { 308 Collections.addAll(mPackages, packages); 309 } 310 } 311 getAppRestrictBackground()312 private boolean getAppRestrictBackground() { 313 final int uid = mAppItem.key; 314 final int uidPolicy = services.mPolicyManager.getUidPolicy(uid); 315 return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0 316 && DynamicDenylistManager.getInstance(mContext).isInManualDenylist(uid); 317 } 318 getUnrestrictData()319 private boolean getUnrestrictData() { 320 if (mDataSaverBackend != null) { 321 return mDataSaverBackend.isAllowlisted(mAppItem.key); 322 } 323 return false; 324 } 325 326 @VisibleForTesting setupIntroPreference()327 void setupIntroPreference() { 328 final Preference pref = getPreferenceScreen().findPreference(ARG_APP_HEADER); 329 if (pref != null) { 330 pref.setIcon(mIcon); 331 pref.setTitle(mLabel); 332 pref.setSelectable(true); 333 } 334 } 335 336 @Override onPreferenceTreeClick(Preference preference)337 public boolean onPreferenceTreeClick(Preference preference) { 338 if (!(preference instanceof IntroPreference)) return false; 339 340 String pkg = !mPackages.isEmpty() ? mPackages.valueAt(0) : null; 341 if (mAppItem.key > 0 && pkg != null) { 342 try { 343 int uid = mPackageManager.getPackageUidAsUser(pkg, 344 UserHandle.getUserId(mAppItem.key)); 345 startAppInfoSettings(pkg, uid, this, 0 /* request */, 346 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider() 347 .getMetricsCategory(this)); 348 } catch (PackageManager.NameNotFoundException e) { 349 Log.w(TAG, "Skipping UID because cannot find package " + pkg); 350 } 351 } 352 return true; 353 } 354 355 @Override getMetricsCategory()356 public int getMetricsCategory() { 357 return SettingsEnums.APP_DATA_USAGE; 358 } 359 360 @Override onDataSaverChanged(boolean isDataSaving)361 public void onDataSaverChanged(boolean isDataSaving) { 362 363 } 364 365 @Override onAllowlistStatusChanged(int uid, boolean isAllowlisted)366 public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) { 367 if (mAppItem.uids.get(uid, false)) { 368 updatePrefs(getAppRestrictBackground(), isAllowlisted); 369 } 370 } 371 372 @Override onDenylistStatusChanged(int uid, boolean isDenylisted)373 public void onDenylistStatusChanged(int uid, boolean isDenylisted) { 374 if (mAppItem.uids.get(uid, false)) { 375 updatePrefs(isDenylisted, getUnrestrictData()); 376 } 377 } 378 } 379