1 /*
2  * Copyright (C) 2016 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 android.app.LoaderManager;
18 import android.content.Context;
19 import android.content.Intent;
20 import android.content.Loader;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.UserInfo;
24 import android.graphics.drawable.Drawable;
25 import android.net.INetworkStatsSession;
26 import android.net.NetworkPolicy;
27 import android.net.NetworkStatsHistory;
28 import android.net.NetworkTemplate;
29 import android.net.TrafficStats;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.support.v14.preference.SwitchPreference;
37 import android.support.v7.preference.Preference;
38 import android.support.v7.preference.PreferenceCategory;
39 import android.text.format.Formatter;
40 import android.util.ArraySet;
41 import android.view.View;
42 import android.widget.AdapterView;
43 import com.android.internal.logging.MetricsProto.MetricsEvent;
44 import com.android.settings.AppHeader;
45 import com.android.settings.R;
46 import com.android.settings.applications.AppInfoBase;
47 import com.android.settingslib.AppItem;
48 import com.android.settingslib.Utils;
49 import com.android.settingslib.net.ChartData;
50 import com.android.settingslib.net.ChartDataLoader;
51 import com.android.settingslib.net.UidDetailProvider;
52 
53 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
54 
55 public class AppDataUsage extends DataUsageBase implements Preference.OnPreferenceChangeListener,
56         DataSaverBackend.Listener {
57 
58     public static final String ARG_APP_ITEM = "app_item";
59     public static final String ARG_NETWORK_TEMPLATE = "network_template";
60 
61     private static final String KEY_TOTAL_USAGE = "total_usage";
62     private static final String KEY_FOREGROUND_USAGE = "foreground_usage";
63     private static final String KEY_BACKGROUND_USAGE = "background_usage";
64     private static final String KEY_APP_SETTINGS = "app_settings";
65     private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
66     private static final String KEY_APP_LIST = "app_list";
67     private static final String KEY_CYCLE = "cycle";
68     private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
69 
70     private static final int LOADER_CHART_DATA = 2;
71 
72     private final ArraySet<String> mPackages = new ArraySet<>();
73     private Preference mTotalUsage;
74     private Preference mForegroundUsage;
75     private Preference mBackgroundUsage;
76     private Preference mAppSettings;
77     private SwitchPreference mRestrictBackground;
78     private PreferenceCategory mAppList;
79 
80     private Drawable mIcon;
81     private CharSequence mLabel;
82     private String mPackageName;
83     private INetworkStatsSession mStatsSession;
84     private CycleAdapter mCycleAdapter;
85 
86     private long mStart;
87     private long mEnd;
88     private ChartData mChartData;
89     private NetworkTemplate mTemplate;
90     private NetworkPolicy mPolicy;
91     private AppItem mAppItem;
92     private Intent mAppSettingsIntent;
93     private SpinnerPreference mCycle;
94     private SwitchPreference mUnrestrictedData;
95     private DataSaverBackend mDataSaverBackend;
96 
97     @Override
onCreate(Bundle icicle)98     public void onCreate(Bundle icicle) {
99         super.onCreate(icicle);
100         final Bundle args = getArguments();
101 
102         try {
103             mStatsSession = services.mStatsService.openSession();
104         } catch (RemoteException e) {
105             throw new RuntimeException(e);
106         }
107 
108         mAppItem = (args != null) ? (AppItem) args.getParcelable(ARG_APP_ITEM) : null;
109         mTemplate = (args != null) ? (NetworkTemplate) args.getParcelable(ARG_NETWORK_TEMPLATE)
110                 : null;
111         if (mTemplate == null) {
112             Context context = getContext();
113             mTemplate = DataUsageSummary.getDefaultTemplate(context,
114                     DataUsageSummary.getDefaultSubscriptionId(context));
115         }
116         if (mAppItem == null) {
117             int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1)
118                     : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1);
119             if (uid == -1) {
120                 // TODO: Log error.
121                 getActivity().finish();
122             } else {
123                 addUid(uid);
124                 mAppItem = new AppItem(uid);
125                 mAppItem.addUid(uid);
126             }
127         } else {
128             for (int i = 0; i < mAppItem.uids.size(); i++) {
129                 addUid(mAppItem.uids.keyAt(i));
130             }
131         }
132         addPreferencesFromResource(R.xml.app_data_usage);
133 
134         mTotalUsage = findPreference(KEY_TOTAL_USAGE);
135         mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
136         mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);
137 
138         mCycle = (SpinnerPreference) findPreference(KEY_CYCLE);
139         mCycleAdapter = new CycleAdapter(getContext(), mCycle, mCycleListener, false);
140 
141         if (mAppItem.key > 0) {
142             if (mPackages.size() != 0) {
143                 PackageManager pm = getPackageManager();
144                 try {
145                     ApplicationInfo info = pm.getApplicationInfo(mPackages.valueAt(0), 0);
146                     mIcon = info.loadIcon(pm);
147                     mLabel = info.loadLabel(pm);
148                     mPackageName = info.packageName;
149                 } catch (PackageManager.NameNotFoundException e) {
150                 }
151             }
152             if (mAppItem.key == Process.SYSTEM_UID) {
153                 removePreference(KEY_UNRESTRICTED_DATA);
154                 removePreference(KEY_RESTRICT_BACKGROUND);
155             } else {
156                 mRestrictBackground = (SwitchPreference) findPreference(KEY_RESTRICT_BACKGROUND);
157                 mRestrictBackground.setOnPreferenceChangeListener(this);
158                 mUnrestrictedData = (SwitchPreference) findPreference(KEY_UNRESTRICTED_DATA);
159                 mUnrestrictedData.setOnPreferenceChangeListener(this);
160             }
161             mDataSaverBackend = new DataSaverBackend(getContext());
162             mAppSettings = findPreference(KEY_APP_SETTINGS);
163 
164             mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
165             mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
166 
167             PackageManager pm = getPackageManager();
168             boolean matchFound = false;
169             for (String packageName : mPackages) {
170                 mAppSettingsIntent.setPackage(packageName);
171                 if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
172                     matchFound = true;
173                     break;
174                 }
175             }
176             if (!matchFound) {
177                 removePreference(KEY_APP_SETTINGS);
178                 mAppSettings = null;
179             }
180 
181             if (mPackages.size() > 1) {
182                 mAppList = (PreferenceCategory) findPreference(KEY_APP_LIST);
183                 for (int i = 1 ; i < mPackages.size(); i++) {
184                     new AppPrefLoader().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
185                             mPackages.valueAt(i));
186                 }
187             } else {
188                 removePreference(KEY_APP_LIST);
189             }
190         } else {
191             if (mAppItem.key == TrafficStats.UID_REMOVED) {
192                 mLabel = getContext().getString(R.string.data_usage_uninstalled_apps_users);
193             } else if (mAppItem.key == TrafficStats.UID_TETHERING) {
194                 mLabel = getContext().getString(R.string.tether_settings_title_all);
195             } else {
196                 final int userId = UidDetailProvider.getUserIdForKey(mAppItem.key);
197                 final UserManager um = UserManager.get(getActivity());
198                 final UserInfo info = um.getUserInfo(userId);
199                 final PackageManager pm = getPackageManager();
200                 mIcon = Utils.getUserIcon(getActivity(), um, info);
201                 mLabel = Utils.getUserLabel(getActivity(), info);
202                 mPackageName = getActivity().getPackageName();
203             }
204             removePreference(KEY_UNRESTRICTED_DATA);
205             removePreference(KEY_APP_SETTINGS);
206             removePreference(KEY_RESTRICT_BACKGROUND);
207             removePreference(KEY_APP_LIST);
208         }
209     }
210 
211     @Override
onDestroy()212     public void onDestroy() {
213         TrafficStats.closeQuietly(mStatsSession);
214         super.onDestroy();
215     }
216 
217     @Override
onResume()218     public void onResume() {
219         super.onResume();
220         if (mDataSaverBackend != null) {
221             mDataSaverBackend.addListener(this);
222         }
223         mPolicy = services.mPolicyEditor.getPolicy(mTemplate);
224         getLoaderManager().restartLoader(LOADER_CHART_DATA,
225                 ChartDataLoader.buildArgs(mTemplate, mAppItem), mChartDataCallbacks);
226         updatePrefs();
227     }
228 
229     @Override
onPause()230     public void onPause() {
231         super.onPause();
232         if (mDataSaverBackend != null) {
233             mDataSaverBackend.remListener(this);
234         }
235     }
236 
237     @Override
onPreferenceChange(Preference preference, Object newValue)238     public boolean onPreferenceChange(Preference preference, Object newValue) {
239         if (preference == mRestrictBackground) {
240             mDataSaverBackend.setIsBlacklisted(mAppItem.key, mPackageName, !(Boolean) newValue);
241             return true;
242         } else if (preference == mUnrestrictedData) {
243             mDataSaverBackend.setIsWhitelisted(mAppItem.key, mPackageName, (Boolean) newValue);
244             return true;
245         }
246         return false;
247     }
248 
249     @Override
onPreferenceTreeClick(Preference preference)250     public boolean onPreferenceTreeClick(Preference preference) {
251         if (preference == mAppSettings) {
252             // TODO: target towards entire UID instead of just first package
253             getActivity().startActivityAsUser(mAppSettingsIntent, new UserHandle(
254                     UserHandle.getUserId(mAppItem.key)));
255             return true;
256         }
257         return super.onPreferenceTreeClick(preference);
258     }
259 
updatePrefs()260     private void updatePrefs() {
261         updatePrefs(getAppRestrictBackground(), getUnrestrictData());
262     }
263 
updatePrefs(boolean restrictBackground, boolean unrestrictData)264     private void updatePrefs(boolean restrictBackground, boolean unrestrictData) {
265         if (mRestrictBackground != null) {
266             mRestrictBackground.setChecked(!restrictBackground);
267         }
268         if (mUnrestrictedData != null) {
269             if (restrictBackground) {
270                 mUnrestrictedData.setVisible(false);
271             } else {
272                 mUnrestrictedData.setVisible(true);
273                 mUnrestrictedData.setChecked(unrestrictData);
274             }
275         }
276     }
277 
addUid(int uid)278     private void addUid(int uid) {
279         String[] packages = getPackageManager().getPackagesForUid(uid);
280         if (packages != null) {
281             for (int i = 0; i < packages.length; i++) {
282                 mPackages.add(packages[i]);
283             }
284         }
285     }
286 
bindData()287     private void bindData() {
288         final long backgroundBytes, foregroundBytes;
289         if (mChartData == null || mStart == 0) {
290             backgroundBytes = foregroundBytes = 0;
291             mCycle.setVisible(false);
292         } else {
293             mCycle.setVisible(true);
294             final long now = System.currentTimeMillis();
295             NetworkStatsHistory.Entry entry = null;
296             entry = mChartData.detailDefault.getValues(mStart, mEnd, now, entry);
297             backgroundBytes = entry.rxBytes + entry.txBytes;
298             entry = mChartData.detailForeground.getValues(mStart, mEnd, now, entry);
299             foregroundBytes = entry.rxBytes + entry.txBytes;
300         }
301         final long totalBytes = backgroundBytes + foregroundBytes;
302         final Context context = getContext();
303 
304         mTotalUsage.setSummary(Formatter.formatFileSize(context, totalBytes));
305         mForegroundUsage.setSummary(Formatter.formatFileSize(context, foregroundBytes));
306         mBackgroundUsage.setSummary(Formatter.formatFileSize(context, backgroundBytes));
307     }
308 
getAppRestrictBackground()309     private boolean getAppRestrictBackground() {
310         final int uid = mAppItem.key;
311         final int uidPolicy = services.mPolicyManager.getUidPolicy(uid);
312         return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
313     }
314 
getUnrestrictData()315     private boolean getUnrestrictData() {
316         if (mDataSaverBackend != null) {
317             return mDataSaverBackend.isWhitelisted(mAppItem.key);
318         }
319         return false;
320     }
321 
322     @Override
onViewCreated(View view, Bundle savedInstanceState)323     public void onViewCreated(View view, Bundle savedInstanceState) {
324         super.onViewCreated(view, savedInstanceState);
325 
326         View header = setPinnedHeaderView(R.layout.app_header);
327         String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null;
328         int uid = 0;
329         try {
330             uid = pkg != null ? getPackageManager().getPackageUid(pkg, 0) : 0;
331         } catch (PackageManager.NameNotFoundException e) {
332         }
333         AppHeader.setupHeaderView(getActivity(), mIcon, mLabel,
334                 pkg, uid, AppHeader.includeAppInfo(this), 0, header, null);
335     }
336 
337     @Override
getMetricsCategory()338     protected int getMetricsCategory() {
339         return MetricsEvent.APP_DATA_USAGE;
340     }
341 
342     private AdapterView.OnItemSelectedListener mCycleListener =
343             new AdapterView.OnItemSelectedListener() {
344         @Override
345         public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
346             final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) mCycle.getSelectedItem();
347 
348             mStart = cycle.start;
349             mEnd = cycle.end;
350             bindData();
351         }
352 
353         @Override
354         public void onNothingSelected(AdapterView<?> parent) {
355             // ignored
356         }
357     };
358 
359     private final LoaderManager.LoaderCallbacks<ChartData> mChartDataCallbacks =
360             new LoaderManager.LoaderCallbacks<ChartData>() {
361         @Override
362         public Loader<ChartData> onCreateLoader(int id, Bundle args) {
363             return new ChartDataLoader(getActivity(), mStatsSession, args);
364         }
365 
366         @Override
367         public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
368             mChartData = data;
369             mCycleAdapter.updateCycleList(mPolicy, mChartData);
370             bindData();
371         }
372 
373         @Override
374         public void onLoaderReset(Loader<ChartData> loader) {
375         }
376     };
377 
378     private class AppPrefLoader extends AsyncTask<String, Void, Preference> {
379         @Override
doInBackground(String... params)380         protected Preference doInBackground(String... params) {
381             PackageManager pm = getPackageManager();
382             String pkg = params[0];
383             try {
384                 ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
385                 Preference preference = new Preference(getPrefContext());
386                 preference.setIcon(info.loadIcon(pm));
387                 preference.setTitle(info.loadLabel(pm));
388                 preference.setSelectable(false);
389                 return preference;
390             } catch (PackageManager.NameNotFoundException e) {
391             }
392             return null;
393         }
394 
395         @Override
onPostExecute(Preference pref)396         protected void onPostExecute(Preference pref) {
397             if (pref != null && mAppList != null) {
398                 mAppList.addPreference(pref);
399             }
400         }
401     }
402 
403     @Override
onDataSaverChanged(boolean isDataSaving)404     public void onDataSaverChanged(boolean isDataSaving) {
405 
406     }
407 
408     @Override
onWhitelistStatusChanged(int uid, boolean isWhitelisted)409     public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
410         if (mAppItem.uids.get(uid, false)) {
411             updatePrefs(getAppRestrictBackground(), isWhitelisted);
412         }
413     }
414 
415     @Override
onBlacklistStatusChanged(int uid, boolean isBlacklisted)416     public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
417         if (mAppItem.uids.get(uid, false)) {
418             updatePrefs(isBlacklisted, getUnrestrictData());
419         }
420     }
421 }
422