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