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