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 package com.android.settings.datausage;
15 
16 import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfMeteredDataUsageUserControlDisabled;
17 
18 import android.app.Application;
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.os.UserHandle;
22 
23 import androidx.annotation.VisibleForTesting;
24 import androidx.preference.Preference;
25 import androidx.preference.PreferenceScreen;
26 
27 import com.android.settings.R;
28 import com.android.settings.applications.AppStateBaseBridge;
29 import com.android.settings.core.BasePreferenceController;
30 import com.android.settings.dashboard.DashboardFragment;
31 import com.android.settings.overlay.FeatureFactory;
32 import com.android.settingslib.applications.AppUtils;
33 import com.android.settingslib.applications.ApplicationsState;
34 import com.android.settingslib.applications.ApplicationsState.AppEntry;
35 import com.android.settingslib.applications.ApplicationsState.AppFilter;
36 import com.android.settingslib.core.lifecycle.Lifecycle;
37 import com.android.settingslib.core.lifecycle.LifecycleObserver;
38 import com.android.settingslib.core.lifecycle.events.OnDestroy;
39 import com.android.settingslib.core.lifecycle.events.OnStart;
40 import com.android.settingslib.core.lifecycle.events.OnStop;
41 
42 import java.util.ArrayList;
43 import java.util.Set;
44 import java.util.TreeSet;
45 
46 public class UnrestrictedDataAccessPreferenceController extends BasePreferenceController implements
47         LifecycleObserver, OnStart, OnStop, OnDestroy, ApplicationsState.Callbacks,
48         AppStateBaseBridge.Callback, Preference.OnPreferenceChangeListener {
49 
50     private final ApplicationsState mApplicationsState;
51     private final AppStateDataUsageBridge mDataUsageBridge;
52     private final DataSaverBackend mDataSaverBackend;
53     private ApplicationsState.Session mSession;
54     private AppFilter mFilter;
55     private DashboardFragment mParentFragment;
56     private PreferenceScreen mScreen;
57     private boolean mExtraLoaded;
58 
UnrestrictedDataAccessPreferenceController(Context context, String key)59     public UnrestrictedDataAccessPreferenceController(Context context, String key) {
60         super(context, key);
61         mApplicationsState = ApplicationsState.getInstance(
62                 (Application) context.getApplicationContext());
63         mDataSaverBackend = new DataSaverBackend(context);
64         mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
65     }
66 
setFilter(AppFilter filter)67     public void setFilter(AppFilter filter) {
68         mFilter = filter;
69     }
70 
setParentFragment(DashboardFragment parentFragment)71     public void setParentFragment(DashboardFragment parentFragment) {
72         mParentFragment = parentFragment;
73     }
74 
setSession(Lifecycle lifecycle)75     public void setSession(Lifecycle lifecycle) {
76         mSession = mApplicationsState.newSession(this, lifecycle);
77     }
78 
79     @Override
displayPreference(PreferenceScreen screen)80     public void displayPreference(PreferenceScreen screen) {
81         super.displayPreference(screen);
82         mScreen = screen;
83     }
84 
85     @Override
getAvailabilityStatus()86     public int getAvailabilityStatus() {
87         return mContext.getResources().getBoolean(R.bool.config_show_data_saver)
88                 ? AVAILABLE_UNSEARCHABLE
89                 : UNSUPPORTED_ON_DEVICE;
90     }
91 
92     @Override
onStart()93     public void onStart() {
94         mDataUsageBridge.resume(true /* forceLoadAllApps */);
95     }
96 
97     @Override
onStop()98     public void onStop() {
99         mDataUsageBridge.pause();
100     }
101 
102     @Override
onDestroy()103     public void onDestroy() {
104         mDataUsageBridge.release();
105     }
106 
107     @Override
onExtraInfoUpdated()108     public void onExtraInfoUpdated() {
109         mExtraLoaded = true;
110         rebuild();
111     }
112 
113     @Override
onRunningStateChanged(boolean running)114     public void onRunningStateChanged(boolean running) {
115 
116     }
117 
118     @Override
onPackageListChanged()119     public void onPackageListChanged() {
120 
121     }
122 
123     @Override
onRebuildComplete(ArrayList<AppEntry> apps)124     public void onRebuildComplete(ArrayList<AppEntry> apps) {
125         if (apps == null) {
126             return;
127         }
128 
129         // Preload top visible icons of app list.
130         AppUtils.preloadTopIcons(mContext, apps,
131                 mContext.getResources().getInteger(R.integer.config_num_visible_app_icons));
132 
133         // Create apps key set for removing useless preferences
134         final Set<String> appsKeySet = new TreeSet<>();
135         // Add or update preferences
136         final int N = apps.size();
137         for (int i = 0; i < N; i++) {
138             final AppEntry entry = apps.get(i);
139             if (!shouldAddPreference(entry)) {
140                 continue;
141             }
142             final String prefkey = UnrestrictedDataAccessPreference.generateKey(entry);
143             appsKeySet.add(prefkey);
144             UnrestrictedDataAccessPreference preference =
145                     (UnrestrictedDataAccessPreference) mScreen.findPreference(prefkey);
146             if (preference == null) {
147                 preference = new UnrestrictedDataAccessPreference(mScreen.getContext(), entry,
148                         mApplicationsState, mDataSaverBackend, mParentFragment);
149                 preference.setOnPreferenceChangeListener(this);
150                 mScreen.addPreference(preference);
151             } else {
152                 preference.setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled(mContext,
153                         entry.info.packageName, UserHandle.getUserId(entry.info.uid)));
154                 preference.checkEcmRestrictionAndSetDisabled(entry.info.packageName);
155                 preference.updateState();
156             }
157             preference.setOrder(i);
158         }
159 
160         // Remove useless preferences
161         removeUselessPrefs(appsKeySet);
162     }
163 
164     @Override
onPackageIconChanged()165     public void onPackageIconChanged() {
166 
167     }
168 
169     @Override
onPackageSizeChanged(String packageName)170     public void onPackageSizeChanged(String packageName) {
171 
172     }
173 
174     @Override
onAllSizesComputed()175     public void onAllSizesComputed() {
176 
177     }
178 
179     @Override
onLauncherInfoChanged()180     public void onLauncherInfoChanged() {
181 
182     }
183 
184     @Override
onLoadEntriesCompleted()185     public void onLoadEntriesCompleted() {
186 
187     }
188 
189     @Override
onPreferenceChange(Preference preference, Object newValue)190     public boolean onPreferenceChange(Preference preference, Object newValue) {
191         if (preference instanceof UnrestrictedDataAccessPreference) {
192             final UnrestrictedDataAccessPreference
193                     accessPreference = (UnrestrictedDataAccessPreference) preference;
194             boolean allowlisted = newValue == Boolean.TRUE;
195             logSpecialPermissionChange(allowlisted, accessPreference.getEntry().info.packageName);
196             mDataSaverBackend.setIsAllowlisted(accessPreference.getEntry().info.uid,
197                     accessPreference.getEntry().info.packageName, allowlisted);
198             if (accessPreference.getDataUsageState() != null) {
199                 accessPreference.getDataUsageState().isDataSaverAllowlisted = allowlisted;
200             }
201             return true;
202         }
203         return false;
204     }
205 
rebuild()206     public void rebuild() {
207         if (!mExtraLoaded) {
208             return;
209         }
210 
211         final ArrayList<AppEntry> apps = mSession.rebuild(mFilter,
212                 ApplicationsState.ALPHA_COMPARATOR);
213         if (apps != null) {
214             onRebuildComplete(apps);
215         }
216     }
217 
removeUselessPrefs(final Set<String> appsKeySet)218     private void removeUselessPrefs(final Set<String> appsKeySet) {
219         final int prefCount = mScreen.getPreferenceCount();
220         String prefKey;
221         if (prefCount > 0) {
222             for (int i = prefCount - 1; i >= 0; i--) {
223                 Preference pref = mScreen.getPreference(i);
224                 prefKey = pref.getKey();
225                 if (!appsKeySet.isEmpty() && appsKeySet.contains(prefKey)) {
226                     continue;
227                 }
228                 mScreen.removePreference(pref);
229             }
230         }
231     }
232 
233     @VisibleForTesting
logSpecialPermissionChange(boolean allowlisted, String packageName)234     void logSpecialPermissionChange(boolean allowlisted, String packageName) {
235         final int logCategory = allowlisted ? SettingsEnums.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW
236                 : SettingsEnums.APP_SPECIAL_PERMISSION_UNL_DATA_DENY;
237         FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(mContext,
238                 logCategory, packageName);
239     }
240 
241     @VisibleForTesting
shouldAddPreference(AppEntry app)242     static boolean shouldAddPreference(AppEntry app) {
243         return app != null && UserHandle.isApp(app.info.uid);
244     }
245 }
246