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