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.checkIfMeteredDataRestricted;
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.ApplicationsState;
33 import com.android.settingslib.applications.ApplicationsState.AppEntry;
34 import com.android.settingslib.applications.ApplicationsState.AppFilter;
35 import com.android.settingslib.core.lifecycle.Lifecycle;
36 import com.android.settingslib.core.lifecycle.LifecycleObserver;
37 import com.android.settingslib.core.lifecycle.events.OnDestroy;
38 import com.android.settingslib.core.lifecycle.events.OnStart;
39 import com.android.settingslib.core.lifecycle.events.OnStop;
40 
41 import java.util.ArrayList;
42 import java.util.Set;
43 import java.util.TreeSet;
44 
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();
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         // Create apps key set for removing useless preferences
130         final Set<String> appsKeySet = new TreeSet<>();
131         // Add or update preferences
132         final int N = apps.size();
133         for (int i = 0; i < N; i++) {
134             final AppEntry entry = apps.get(i);
135             if (!shouldAddPreference(entry)) {
136                 continue;
137             }
138             final String prefkey = UnrestrictedDataAccessPreference.generateKey(entry);
139             appsKeySet.add(prefkey);
140             UnrestrictedDataAccessPreference preference =
141                     (UnrestrictedDataAccessPreference) mScreen.findPreference(prefkey);
142             if (preference == null) {
143                 preference = new UnrestrictedDataAccessPreference(mScreen.getContext(), entry,
144                         mApplicationsState, mDataSaverBackend, mParentFragment);
145                 preference.setOnPreferenceChangeListener(this);
146                 mScreen.addPreference(preference);
147             } else {
148                 preference.setDisabledByAdmin(checkIfMeteredDataRestricted(mContext,
149                         entry.info.packageName, UserHandle.getUserId(entry.info.uid)));
150                 preference.updateState();
151             }
152             preference.setOrder(i);
153         }
154 
155         // Remove useless preferences
156         removeUselessPrefs(appsKeySet);
157     }
158 
159     @Override
onPackageIconChanged()160     public void onPackageIconChanged() {
161 
162     }
163 
164     @Override
onPackageSizeChanged(String packageName)165     public void onPackageSizeChanged(String packageName) {
166 
167     }
168 
169     @Override
onAllSizesComputed()170     public void onAllSizesComputed() {
171 
172     }
173 
174     @Override
onLauncherInfoChanged()175     public void onLauncherInfoChanged() {
176 
177     }
178 
179     @Override
onLoadEntriesCompleted()180     public void onLoadEntriesCompleted() {
181 
182     }
183 
184     @Override
onPreferenceChange(Preference preference, Object newValue)185     public boolean onPreferenceChange(Preference preference, Object newValue) {
186         if (preference instanceof UnrestrictedDataAccessPreference) {
187             final UnrestrictedDataAccessPreference
188                     accessPreference = (UnrestrictedDataAccessPreference) preference;
189             boolean whitelisted = newValue == Boolean.TRUE;
190             logSpecialPermissionChange(whitelisted, accessPreference.getEntry().info.packageName);
191             mDataSaverBackend.setIsWhitelisted(accessPreference.getEntry().info.uid,
192                     accessPreference.getEntry().info.packageName, whitelisted);
193             accessPreference.getDataUsageState().isDataSaverWhitelisted = whitelisted;
194             return true;
195         }
196         return false;
197     }
198 
rebuild()199     public void rebuild() {
200         if (!mExtraLoaded) {
201             return;
202         }
203 
204         final ArrayList<AppEntry> apps = mSession.rebuild(mFilter,
205                 ApplicationsState.ALPHA_COMPARATOR);
206         if (apps != null) {
207             onRebuildComplete(apps);
208         }
209     }
210 
removeUselessPrefs(final Set<String> appsKeySet)211     private void removeUselessPrefs(final Set<String> appsKeySet) {
212         final int prefCount = mScreen.getPreferenceCount();
213         String prefKey;
214         if (prefCount > 0) {
215             for (int i = prefCount - 1; i >= 0; i--) {
216                 Preference pref = mScreen.getPreference(i);
217                 prefKey = pref.getKey();
218                 if (!appsKeySet.isEmpty() && appsKeySet.contains(prefKey)) {
219                     continue;
220                 }
221                 mScreen.removePreference(pref);
222             }
223         }
224     }
225 
226     @VisibleForTesting
logSpecialPermissionChange(boolean whitelisted, String packageName)227     void logSpecialPermissionChange(boolean whitelisted, String packageName) {
228         final int logCategory = whitelisted ? SettingsEnums.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW
229                 : SettingsEnums.APP_SPECIAL_PERMISSION_UNL_DATA_DENY;
230         FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext,
231                 logCategory, packageName);
232     }
233 
234     @VisibleForTesting
shouldAddPreference(AppEntry app)235     static boolean shouldAddPreference(AppEntry app) {
236         return app != null && UserHandle.isApp(app.info.uid);
237     }
238 }
239