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 static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 18 19 import android.app.Activity; 20 import android.app.LoaderManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.Loader; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.UserInfo; 27 import android.graphics.drawable.Drawable; 28 import android.net.INetworkStatsSession; 29 import android.net.NetworkPolicy; 30 import android.net.NetworkStatsHistory; 31 import android.net.NetworkTemplate; 32 import android.net.TrafficStats; 33 import android.os.Bundle; 34 import android.os.RemoteException; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.support.v14.preference.SwitchPreference; 38 import android.support.v7.preference.Preference; 39 import android.support.v7.preference.PreferenceCategory; 40 import android.text.format.Formatter; 41 import android.util.ArraySet; 42 import android.util.IconDrawableFactory; 43 import android.view.View; 44 import android.widget.AdapterView; 45 46 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 47 import com.android.settings.R; 48 import com.android.settings.applications.AppHeaderController; 49 import com.android.settings.applications.AppInfoBase; 50 import com.android.settings.overlay.FeatureFactory; 51 import com.android.settingslib.AppItem; 52 import com.android.settingslib.Utils; 53 import com.android.settingslib.net.ChartData; 54 import com.android.settingslib.net.ChartDataLoader; 55 import com.android.settingslib.net.UidDetailProvider; 56 57 public 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 onCreate(Bundle icicle)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 onDestroy()217 public void onDestroy() { 218 TrafficStats.closeQuietly(mStatsSession); 219 super.onDestroy(); 220 } 221 222 @Override onResume()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 onPause()235 public void onPause() { 236 super.onPause(); 237 if (mDataSaverBackend != null) { 238 mDataSaverBackend.remListener(this); 239 } 240 } 241 242 @Override onPreferenceChange(Preference preference, Object newValue)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 onPreferenceTreeClick(Preference preference)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 updatePrefs()265 private void updatePrefs() { 266 updatePrefs(getAppRestrictBackground(), getUnrestrictData()); 267 } 268 updatePrefs(boolean restrictBackground, boolean unrestrictData)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 addUid(int uid)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 bindData()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 getAppRestrictBackground()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 getUnrestrictData()320 private boolean getUnrestrictData() { 321 if (mDataSaverBackend != null) { 322 return mDataSaverBackend.isWhitelisted(mAppItem.key); 323 } 324 return false; 325 } 326 327 @Override onViewCreated(View view, Bundle savedInstanceState)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 getMetricsCategory()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 onDataSaverChanged(boolean isDataSaving)416 public void onDataSaverChanged(boolean isDataSaving) { 417 418 } 419 420 @Override onWhitelistStatusChanged(int uid, boolean isWhitelisted)421 public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { 422 if (mAppItem.uids.get(uid, false)) { 423 updatePrefs(getAppRestrictBackground(), isWhitelisted); 424 } 425 } 426 427 @Override onBlacklistStatusChanged(int uid, boolean isBlacklisted)428 public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { 429 if (mAppItem.uids.get(uid, false)) { 430 updatePrefs(isBlacklisted, getUnrestrictData()); 431 } 432 } 433 } 434