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