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