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 com.android.settingslib.RestrictedLockUtils.checkIfMeteredDataRestricted; 18 19 import android.app.Application; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.os.UserHandle; 23 import android.support.v7.preference.Preference; 24 import android.support.v7.preference.PreferenceViewHolder; 25 import android.view.Menu; 26 import android.view.MenuInflater; 27 import android.view.MenuItem; 28 import android.view.View; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 32 import com.android.settings.R; 33 import com.android.settings.SettingsPreferenceFragment; 34 import com.android.settings.applications.AppStateBaseBridge; 35 import com.android.settings.applications.appinfo.AppInfoDashboardFragment; 36 import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; 37 import com.android.settings.overlay.FeatureFactory; 38 import com.android.settings.widget.AppSwitchPreference; 39 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 40 import com.android.settingslib.RestrictedPreferenceHelper; 41 import com.android.settingslib.applications.ApplicationsState; 42 import com.android.settingslib.applications.ApplicationsState.AppEntry; 43 import com.android.settingslib.applications.ApplicationsState.AppFilter; 44 45 import java.util.ArrayList; 46 47 public class UnrestrictedDataAccess extends SettingsPreferenceFragment 48 implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback, 49 Preference.OnPreferenceChangeListener { 50 51 private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 42; 52 private static final String EXTRA_SHOW_SYSTEM = "show_system"; 53 54 private ApplicationsState mApplicationsState; 55 private AppStateDataUsageBridge mDataUsageBridge; 56 private ApplicationsState.Session mSession; 57 private DataSaverBackend mDataSaverBackend; 58 private boolean mShowSystem; 59 private boolean mExtraLoaded; 60 private AppFilter mFilter; 61 62 @Override onCreate(Bundle icicle)63 public void onCreate(Bundle icicle) { 64 super.onCreate(icicle); 65 setAnimationAllowed(true); 66 mApplicationsState = ApplicationsState.getInstance( 67 (Application) getContext().getApplicationContext()); 68 mDataSaverBackend = new DataSaverBackend(getContext()); 69 mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); 70 mSession = mApplicationsState.newSession(this, getLifecycle()); 71 mShowSystem = icicle != null && icicle.getBoolean(EXTRA_SHOW_SYSTEM); 72 mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED 73 : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; 74 setHasOptionsMenu(true); 75 } 76 77 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)78 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 79 menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, 80 mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system); 81 super.onCreateOptionsMenu(menu, inflater); 82 } 83 84 @Override onOptionsItemSelected(MenuItem item)85 public boolean onOptionsItemSelected(MenuItem item) { 86 switch (item.getItemId()) { 87 case MENU_SHOW_SYSTEM: 88 mShowSystem = !mShowSystem; 89 item.setTitle(mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system); 90 mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED 91 : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; 92 if (mExtraLoaded) { 93 rebuild(); 94 } 95 break; 96 } 97 return super.onOptionsItemSelected(item); 98 } 99 100 @Override onSaveInstanceState(Bundle outState)101 public void onSaveInstanceState(Bundle outState) { 102 super.onSaveInstanceState(outState); 103 outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); 104 } 105 106 @Override onViewCreated(View view, Bundle savedInstanceState)107 public void onViewCreated(View view, Bundle savedInstanceState) { 108 super.onViewCreated(view, savedInstanceState); 109 setLoading(true, false); 110 } 111 112 @Override onResume()113 public void onResume() { 114 super.onResume(); 115 mDataUsageBridge.resume(); 116 } 117 118 @Override onPause()119 public void onPause() { 120 super.onPause(); 121 mDataUsageBridge.pause(); 122 } 123 124 @Override onDestroy()125 public void onDestroy() { 126 super.onDestroy(); 127 mDataUsageBridge.release(); 128 } 129 130 @Override onExtraInfoUpdated()131 public void onExtraInfoUpdated() { 132 mExtraLoaded = true; 133 rebuild(); 134 } 135 136 @Override getHelpResource()137 public int getHelpResource() { 138 return R.string.help_url_unrestricted_data_access; 139 } 140 rebuild()141 private void rebuild() { 142 ArrayList<AppEntry> apps = mSession.rebuild(mFilter, ApplicationsState.ALPHA_COMPARATOR); 143 if (apps != null) { 144 onRebuildComplete(apps); 145 } 146 } 147 148 @Override onRunningStateChanged(boolean running)149 public void onRunningStateChanged(boolean running) { 150 151 } 152 153 @Override onPackageListChanged()154 public void onPackageListChanged() { 155 156 } 157 158 @Override onRebuildComplete(ArrayList<AppEntry> apps)159 public void onRebuildComplete(ArrayList<AppEntry> apps) { 160 if (getContext() == null) return; 161 cacheRemoveAllPrefs(getPreferenceScreen()); 162 final int N = apps.size(); 163 for (int i = 0; i < N; i++) { 164 AppEntry entry = apps.get(i); 165 if (!shouldAddPreference(entry)) { 166 continue; 167 } 168 String key = entry.info.packageName + "|" + entry.info.uid; 169 AccessPreference preference = (AccessPreference) getCachedPreference(key); 170 if (preference == null) { 171 preference = new AccessPreference(getPrefContext(), entry); 172 preference.setKey(key); 173 preference.setOnPreferenceChangeListener(this); 174 getPreferenceScreen().addPreference(preference); 175 } else { 176 preference.setDisabledByAdmin(checkIfMeteredDataRestricted(getContext(), 177 entry.info.packageName, UserHandle.getUserId(entry.info.uid))); 178 preference.reuse(); 179 } 180 preference.setOrder(i); 181 } 182 setLoading(false, true); 183 removeCachedPrefs(getPreferenceScreen()); 184 } 185 186 @Override onPackageIconChanged()187 public void onPackageIconChanged() { 188 189 } 190 191 @Override onPackageSizeChanged(String packageName)192 public void onPackageSizeChanged(String packageName) { 193 194 } 195 196 @Override onAllSizesComputed()197 public void onAllSizesComputed() { 198 199 } 200 201 @Override onLauncherInfoChanged()202 public void onLauncherInfoChanged() { 203 204 } 205 206 @Override onLoadEntriesCompleted()207 public void onLoadEntriesCompleted() { 208 209 } 210 211 @Override getMetricsCategory()212 public int getMetricsCategory() { 213 return MetricsEvent.DATA_USAGE_UNRESTRICTED_ACCESS; 214 } 215 216 @Override getPreferenceScreenResId()217 protected int getPreferenceScreenResId() { 218 return R.xml.unrestricted_data_access_settings; 219 } 220 221 @Override onPreferenceChange(Preference preference, Object newValue)222 public boolean onPreferenceChange(Preference preference, Object newValue) { 223 if (preference instanceof AccessPreference) { 224 AccessPreference accessPreference = (AccessPreference) preference; 225 boolean whitelisted = newValue == Boolean.TRUE; 226 logSpecialPermissionChange(whitelisted, accessPreference.mEntry.info.packageName); 227 mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid, 228 accessPreference.mEntry.info.packageName, whitelisted); 229 accessPreference.mState.isDataSaverWhitelisted = whitelisted; 230 return true; 231 } 232 return false; 233 } 234 235 @VisibleForTesting logSpecialPermissionChange(boolean whitelisted, String packageName)236 void logSpecialPermissionChange(boolean whitelisted, String packageName) { 237 int logCategory = whitelisted ? MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW 238 : MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY; 239 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(), 240 logCategory, packageName); 241 } 242 243 @VisibleForTesting shouldAddPreference(AppEntry app)244 boolean shouldAddPreference(AppEntry app) { 245 return app != null && UserHandle.isApp(app.info.uid); 246 } 247 248 @VisibleForTesting 249 class AccessPreference extends AppSwitchPreference 250 implements DataSaverBackend.Listener { 251 private final AppEntry mEntry; 252 private final DataUsageState mState; 253 private final RestrictedPreferenceHelper mHelper; 254 AccessPreference(final Context context, AppEntry entry)255 public AccessPreference(final Context context, AppEntry entry) { 256 super(context); 257 setWidgetLayoutResource(R.layout.restricted_switch_widget); 258 mHelper = new RestrictedPreferenceHelper(context, this, null); 259 mEntry = entry; 260 mState = (DataUsageState) mEntry.extraInfo; 261 mEntry.ensureLabel(getContext()); 262 setDisabledByAdmin(checkIfMeteredDataRestricted(context, entry.info.packageName, 263 UserHandle.getUserId(entry.info.uid))); 264 setState(); 265 if (mEntry.icon != null) { 266 setIcon(mEntry.icon); 267 } 268 } 269 270 @Override onAttached()271 public void onAttached() { 272 super.onAttached(); 273 mDataSaverBackend.addListener(this); 274 } 275 276 @Override onDetached()277 public void onDetached() { 278 mDataSaverBackend.remListener(this); 279 super.onDetached(); 280 } 281 282 @Override onClick()283 protected void onClick() { 284 if (mState.isDataSaverBlacklisted) { 285 // app is blacklisted, launch App Data Usage screen 286 AppInfoDashboardFragment.startAppInfoFragment(AppDataUsage.class, 287 R.string.app_data_usage, 288 null /* arguments */, 289 UnrestrictedDataAccess.this, 290 mEntry); 291 } else { 292 // app is not blacklisted, let superclass handle toggle switch 293 super.onClick(); 294 } 295 } 296 297 @Override performClick()298 public void performClick() { 299 if (!mHelper.performClick()) { 300 super.performClick(); 301 } 302 } 303 304 // Sets UI state based on whitelist/blacklist status. setState()305 private void setState() { 306 setTitle(mEntry.label); 307 if (mState != null) { 308 setChecked(mState.isDataSaverWhitelisted); 309 if (isDisabledByAdmin()) { 310 setSummary(R.string.disabled_by_admin); 311 } else if (mState.isDataSaverBlacklisted) { 312 setSummary(R.string.restrict_background_blacklisted); 313 } else { 314 setSummary(""); 315 } 316 } 317 } 318 reuse()319 public void reuse() { 320 setState(); 321 notifyChanged(); 322 } 323 324 @Override onBindViewHolder(PreferenceViewHolder holder)325 public void onBindViewHolder(PreferenceViewHolder holder) { 326 if (mEntry.icon == null) { 327 holder.itemView.post(new Runnable() { 328 @Override 329 public void run() { 330 // Ensure we have an icon before binding. 331 mApplicationsState.ensureIcon(mEntry); 332 // This might trigger us to bind again, but it gives an easy way to only 333 // load the icon once its needed, so its probably worth it. 334 setIcon(mEntry.icon); 335 } 336 }); 337 } 338 final boolean disabledByAdmin = isDisabledByAdmin(); 339 final View widgetFrame = holder.findViewById(android.R.id.widget_frame); 340 if (disabledByAdmin) { 341 widgetFrame.setVisibility(View.VISIBLE); 342 } else { 343 widgetFrame.setVisibility(mState != null && mState.isDataSaverBlacklisted 344 ? View.INVISIBLE : View.VISIBLE); 345 } 346 super.onBindViewHolder(holder); 347 348 mHelper.onBindViewHolder(holder); 349 holder.findViewById(R.id.restricted_icon).setVisibility( 350 disabledByAdmin ? View.VISIBLE : View.GONE); 351 holder.findViewById(android.R.id.switch_widget).setVisibility( 352 disabledByAdmin ? View.GONE : View.VISIBLE); 353 } 354 355 @Override onDataSaverChanged(boolean isDataSaving)356 public void onDataSaverChanged(boolean isDataSaving) { 357 } 358 359 @Override onWhitelistStatusChanged(int uid, boolean isWhitelisted)360 public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { 361 if (mState != null && mEntry.info.uid == uid) { 362 mState.isDataSaverWhitelisted = isWhitelisted; 363 reuse(); 364 } 365 } 366 367 @Override onBlacklistStatusChanged(int uid, boolean isBlacklisted)368 public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { 369 if (mState != null && mEntry.info.uid == uid) { 370 mState.isDataSaverBlacklisted = isBlacklisted; 371 reuse(); 372 } 373 } 374 setDisabledByAdmin(EnforcedAdmin admin)375 public void setDisabledByAdmin(EnforcedAdmin admin) { 376 mHelper.setDisabledByAdmin(admin); 377 } 378 isDisabledByAdmin()379 public boolean isDisabledByAdmin() { 380 return mHelper.isDisabledByAdmin(); 381 } 382 383 @VisibleForTesting getEntryForTest()384 public AppEntry getEntryForTest() { 385 return mEntry; 386 } 387 } 388 389 } 390