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