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.Application; 18 import android.content.Context; 19 import android.os.Bundle; 20 import android.os.UserHandle; 21 import android.support.v7.preference.Preference; 22 import android.support.v7.preference.PreferenceViewHolder; 23 import android.view.Menu; 24 import android.view.MenuInflater; 25 import android.view.MenuItem; 26 import android.view.View; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 30 import com.android.settings.R; 31 import com.android.settings.SettingsPreferenceFragment; 32 import com.android.settings.applications.AppStateBaseBridge; 33 import com.android.settings.applications.InstalledAppDetails; 34 import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; 35 import com.android.settings.overlay.FeatureFactory; 36 import com.android.settings.widget.FilterTouchesSwitchPreference; 37 import com.android.settingslib.applications.ApplicationsState; 38 import com.android.settingslib.applications.ApplicationsState.AppEntry; 39 import com.android.settingslib.applications.ApplicationsState.AppFilter; 40 41 import java.util.ArrayList; 42 43 public class UnrestrictedDataAccess extends SettingsPreferenceFragment 44 implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback, 45 Preference.OnPreferenceChangeListener { 46 47 private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 42; 48 private static final String EXTRA_SHOW_SYSTEM = "show_system"; 49 50 private ApplicationsState mApplicationsState; 51 private AppStateDataUsageBridge mDataUsageBridge; 52 private ApplicationsState.Session mSession; 53 private DataSaverBackend mDataSaverBackend; 54 private boolean mShowSystem; 55 private boolean mExtraLoaded; 56 private AppFilter mFilter; 57 58 @Override onCreate(Bundle icicle)59 public void onCreate(Bundle icicle) { 60 super.onCreate(icicle); 61 setAnimationAllowed(true); 62 setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext())); 63 mApplicationsState = ApplicationsState.getInstance( 64 (Application) getContext().getApplicationContext()); 65 mDataSaverBackend = new DataSaverBackend(getContext()); 66 mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); 67 mSession = mApplicationsState.newSession(this); 68 mShowSystem = icicle != null && icicle.getBoolean(EXTRA_SHOW_SYSTEM); 69 mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED 70 : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; 71 setHasOptionsMenu(true); 72 } 73 74 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)75 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 76 menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, 77 mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system); 78 super.onCreateOptionsMenu(menu, inflater); 79 } 80 81 @Override onOptionsItemSelected(MenuItem item)82 public boolean onOptionsItemSelected(MenuItem item) { 83 switch (item.getItemId()) { 84 case MENU_SHOW_SYSTEM: 85 mShowSystem = !mShowSystem; 86 item.setTitle(mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system); 87 mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED 88 : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; 89 if (mExtraLoaded) { 90 rebuild(); 91 } 92 break; 93 } 94 return super.onOptionsItemSelected(item); 95 } 96 97 @Override onSaveInstanceState(Bundle outState)98 public void onSaveInstanceState(Bundle outState) { 99 super.onSaveInstanceState(outState); 100 outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); 101 } 102 103 @Override onViewCreated(View view, Bundle savedInstanceState)104 public void onViewCreated(View view, Bundle savedInstanceState) { 105 super.onViewCreated(view, savedInstanceState); 106 setLoading(true, false); 107 } 108 109 @Override onResume()110 public void onResume() { 111 super.onResume(); 112 mSession.resume(); 113 mDataUsageBridge.resume(); 114 } 115 116 @Override onPause()117 public void onPause() { 118 super.onPause(); 119 mDataUsageBridge.pause(); 120 mSession.pause(); 121 } 122 123 @Override onDestroy()124 public void onDestroy() { 125 super.onDestroy(); 126 mSession.release(); 127 mDataUsageBridge.release(); 128 } 129 130 @Override onExtraInfoUpdated()131 public void onExtraInfoUpdated() { 132 mExtraLoaded = true; 133 rebuild(); 134 } 135 136 @Override getHelpResource()137 protected 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.reuse(); 177 } 178 preference.setOrder(i); 179 } 180 setLoading(false, true); 181 removeCachedPrefs(getPreferenceScreen()); 182 } 183 184 @Override onPackageIconChanged()185 public void onPackageIconChanged() { 186 187 } 188 189 @Override onPackageSizeChanged(String packageName)190 public void onPackageSizeChanged(String packageName) { 191 192 } 193 194 @Override onAllSizesComputed()195 public void onAllSizesComputed() { 196 197 } 198 199 @Override onLauncherInfoChanged()200 public void onLauncherInfoChanged() { 201 202 } 203 204 @Override onLoadEntriesCompleted()205 public void onLoadEntriesCompleted() { 206 207 } 208 209 @Override getMetricsCategory()210 public int getMetricsCategory() { 211 return MetricsEvent.DATA_USAGE_UNRESTRICTED_ACCESS; 212 } 213 214 @Override onPreferenceChange(Preference preference, Object newValue)215 public boolean onPreferenceChange(Preference preference, Object newValue) { 216 if (preference instanceof AccessPreference) { 217 AccessPreference accessPreference = (AccessPreference) preference; 218 boolean whitelisted = newValue == Boolean.TRUE; 219 logSpecialPermissionChange(whitelisted, accessPreference.mEntry.info.packageName); 220 mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid, 221 accessPreference.mEntry.info.packageName, whitelisted); 222 accessPreference.mState.isDataSaverWhitelisted = whitelisted; 223 return true; 224 } 225 return false; 226 } 227 228 @VisibleForTesting logSpecialPermissionChange(boolean whitelisted, String packageName)229 void logSpecialPermissionChange(boolean whitelisted, String packageName) { 230 int logCategory = whitelisted ? MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW 231 : MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY; 232 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(), 233 logCategory, packageName); 234 } 235 236 @VisibleForTesting shouldAddPreference(AppEntry app)237 boolean shouldAddPreference(AppEntry app) { 238 return app != null && UserHandle.isApp(app.info.uid); 239 } 240 241 private class AccessPreference extends FilterTouchesSwitchPreference implements 242 DataSaverBackend.Listener { 243 private final AppEntry mEntry; 244 private final DataUsageState mState; 245 AccessPreference(final Context context, AppEntry entry)246 public AccessPreference(final Context context, AppEntry entry) { 247 super(context); 248 mEntry = entry; 249 mState = (DataUsageState) mEntry.extraInfo; 250 mEntry.ensureLabel(getContext()); 251 setState(); 252 if (mEntry.icon != null) { 253 setIcon(mEntry.icon); 254 } 255 } 256 257 @Override onAttached()258 public void onAttached() { 259 super.onAttached(); 260 mDataSaverBackend.addListener(this); 261 } 262 263 @Override onDetached()264 public void onDetached() { 265 mDataSaverBackend.remListener(this); 266 super.onDetached(); 267 } 268 269 @Override onClick()270 protected void onClick() { 271 if (mState.isDataSaverBlacklisted) { 272 // app is blacklisted, launch App Data Usage screen 273 InstalledAppDetails.startAppInfoFragment(AppDataUsage.class, 274 getContext().getString(R.string.app_data_usage), 275 UnrestrictedDataAccess.this, 276 mEntry); 277 } else { 278 // app is not blacklisted, let superclass handle toggle switch 279 super.onClick(); 280 } 281 } 282 283 // Sets UI state based on whitelist/blacklist status. setState()284 private void setState() { 285 setTitle(mEntry.label); 286 if (mState != null) { 287 setChecked(mState.isDataSaverWhitelisted); 288 if (mState.isDataSaverBlacklisted) { 289 setSummary(R.string.restrict_background_blacklisted); 290 } else { 291 setSummary(""); 292 } 293 } 294 } 295 reuse()296 public void reuse() { 297 setState(); 298 notifyChanged(); 299 } 300 301 @Override onBindViewHolder(PreferenceViewHolder holder)302 public void onBindViewHolder(PreferenceViewHolder holder) { 303 if (mEntry.icon == null) { 304 holder.itemView.post(new Runnable() { 305 @Override 306 public void run() { 307 // Ensure we have an icon before binding. 308 mApplicationsState.ensureIcon(mEntry); 309 // This might trigger us to bind again, but it gives an easy way to only 310 // load the icon once its needed, so its probably worth it. 311 setIcon(mEntry.icon); 312 } 313 }); 314 } 315 holder.findViewById(android.R.id.widget_frame) 316 .setVisibility(mState != null && mState.isDataSaverBlacklisted 317 ? View.INVISIBLE : View.VISIBLE); 318 super.onBindViewHolder(holder); 319 } 320 321 @Override onDataSaverChanged(boolean isDataSaving)322 public void onDataSaverChanged(boolean isDataSaving) { 323 } 324 325 @Override onWhitelistStatusChanged(int uid, boolean isWhitelisted)326 public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { 327 if (mState != null && mEntry.info.uid == uid) { 328 mState.isDataSaverWhitelisted = isWhitelisted; 329 reuse(); 330 } 331 } 332 333 @Override onBlacklistStatusChanged(int uid, boolean isBlacklisted)334 public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { 335 if (mState != null && mEntry.info.uid == uid) { 336 mState.isDataSaverBlacklisted = isBlacklisted; 337 reuse(); 338 } 339 } 340 } 341 342 } 343