• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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