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