1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.storage; 18 19 import android.app.ActivityManager; 20 import android.car.userlib.CarUserManagerHelper; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.IPackageDataObserver; 25 import android.content.pm.PackageManager; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.Message; 29 import android.os.UserHandle; 30 import android.os.UserManager; 31 import android.view.View; 32 import android.widget.Button; 33 34 import androidx.annotation.LayoutRes; 35 import androidx.annotation.VisibleForTesting; 36 import androidx.annotation.XmlRes; 37 import androidx.loader.app.LoaderManager; 38 39 import com.android.car.settings.R; 40 import com.android.car.settings.common.ConfirmationDialogFragment; 41 import com.android.car.settings.common.Logger; 42 import com.android.car.settings.common.SettingsFragment; 43 import com.android.settingslib.RestrictedLockUtils; 44 import com.android.settingslib.RestrictedLockUtilsInternal; 45 import com.android.settingslib.applications.ApplicationsState; 46 import com.android.settingslib.applications.StorageStatsSource; 47 48 import java.util.Arrays; 49 import java.util.List; 50 51 /** 52 * Fragment to display the applications storage information. Also provide buttons to clear the 53 * applications cache data and user data. 54 */ 55 public class AppStorageSettingsDetailsFragment extends SettingsFragment implements 56 AppsStorageStatsManager.Callback { 57 private static final Logger LOG = new Logger(AppStorageSettingsDetailsFragment.class); 58 59 @VisibleForTesting 60 static final String CONFIRM_CLEAR_STORAGE_DIALOG_TAG = 61 "com.android.car.settings.storage.ConfirmClearStorageDialog"; 62 63 @VisibleForTesting 64 static final String CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG = 65 "com.android.car.settings.storage.ConfirmCannotClearStorageDialog"; 66 67 public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; 68 // Result code identifiers 69 public static final int REQUEST_MANAGE_SPACE = 2; 70 71 // Internal constants used in Handler 72 private static final int OP_SUCCESSFUL = 1; 73 private static final int OP_FAILED = 2; 74 75 // Constant used in handler to determine when the user data is cleared. 76 private static final int MSG_CLEAR_USER_DATA = 1; 77 // Constant used in handler to determine when the cache is cleared. 78 private static final int MSG_CLEAR_CACHE = 2; 79 80 // Keys to save the instance values. 81 private static final String KEY_CACHE_CLEARED = "cache_cleared"; 82 private static final String KEY_DATA_CLEARED = "data_cleared"; 83 84 // Package information 85 protected PackageManager mPackageManager; 86 private String mPackageName; 87 88 // Application state info 89 private ApplicationsState.AppEntry mAppEntry; 90 private ApplicationsState mAppState; 91 private ApplicationInfo mInfo; 92 private AppsStorageStatsManager mAppsStorageStatsManager; 93 94 // User info 95 private int mUserId; 96 private CarUserManagerHelper mCarUserManagerHelper; 97 98 // An observer callback to get notified when the cache file deletion is complete. 99 private ClearCacheObserver mClearCacheObserver; 100 // An observer callback to get notified when the user data deletion is complete. 101 private ClearUserDataObserver mClearDataObserver; 102 103 // The restriction enforced by admin. 104 private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin; 105 private boolean mAppsControlDisallowedBySystem; 106 107 // Clear user data and cache buttons and state. 108 private Button mClearStorageButton; 109 private Button mClearCacheButton; 110 private boolean mCanClearData = true; 111 private boolean mCacheCleared; 112 private boolean mDataCleared; 113 114 private final ConfirmationDialogFragment.ConfirmListener mConfirmClearStorageDialog = 115 arguments -> initiateClearUserData(); 116 117 118 private final ConfirmationDialogFragment.ConfirmListener mConfirmCannotClearStorageDialog = 119 arguments -> mClearStorageButton.setEnabled(false); 120 121 /** Creates an instance of this fragment, passing packageName as an argument. */ getInstance(String packageName)122 public static AppStorageSettingsDetailsFragment getInstance(String packageName) { 123 AppStorageSettingsDetailsFragment applicationDetailFragment = 124 new AppStorageSettingsDetailsFragment(); 125 Bundle bundle = new Bundle(); 126 bundle.putString(EXTRA_PACKAGE_NAME, packageName); 127 applicationDetailFragment.setArguments(bundle); 128 return applicationDetailFragment; 129 } 130 131 @Override 132 @XmlRes getPreferenceScreenResId()133 protected int getPreferenceScreenResId() { 134 return R.xml.app_storage_settings_details_fragment; 135 } 136 137 @Override 138 @LayoutRes getActionBarLayoutId()139 protected int getActionBarLayoutId() { 140 return R.layout.action_bar_with_button; 141 } 142 143 @Override onAttach(Context context)144 public void onAttach(Context context) { 145 super.onAttach(context); 146 mCarUserManagerHelper = new CarUserManagerHelper(context); 147 mUserId = mCarUserManagerHelper.getCurrentProcessUserId(); 148 mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME); 149 mAppState = ApplicationsState.getInstance(requireActivity().getApplication()); 150 mAppEntry = mAppState.getEntry(mPackageName, mUserId); 151 StorageStatsSource storageStatsSource = new StorageStatsSource(context); 152 StorageStatsSource.AppStorageStats stats = null; 153 mPackageManager = context.getPackageManager(); 154 try { 155 stats = storageStatsSource.getStatsForPackage(/* volumeUuid= */ null, mPackageName, 156 UserHandle.of(mUserId)); 157 } catch (Exception e) { 158 // This may happen if the package was removed during our calculation. 159 LOG.w("App unexpectedly not found", e); 160 } 161 mAppsStorageStatsManager = new AppsStorageStatsManager(context); 162 mAppsStorageStatsManager.registerListener(this); 163 use(StorageApplicationPreferenceController.class, 164 R.string.pk_storage_application_details) 165 .setAppEntry(mAppEntry) 166 .setAppState(mAppState); 167 168 List<StorageSizeBasePreferenceController> preferenceControllers = Arrays.asList( 169 use(StorageApplicationSizePreferenceController.class, 170 R.string.pk_storage_application_size), 171 use(StorageApplicationTotalSizePreferenceController.class, 172 R.string.pk_storage_application_total_size), 173 use(StorageApplicationUserDataPreferenceController.class, 174 R.string.pk_storage_application_data_size), 175 use(StorageApplicationCacheSizePreferenceController.class, 176 R.string.pk_storage_application_cache_size) 177 ); 178 179 for (StorageSizeBasePreferenceController pc : preferenceControllers) { 180 pc.setAppsStorageStatsManager(mAppsStorageStatsManager); 181 pc.setAppStorageStats(stats); 182 } 183 } 184 185 @Override onSaveInstanceState(Bundle outState)186 public void onSaveInstanceState(Bundle outState) { 187 super.onSaveInstanceState(outState); 188 outState.putBoolean(KEY_CACHE_CLEARED, mCacheCleared); 189 outState.putBoolean(KEY_DATA_CLEARED, mDataCleared); 190 } 191 192 @Override onCreate(Bundle savedInstanceState)193 public void onCreate(Bundle savedInstanceState) { 194 super.onCreate(savedInstanceState); 195 if (savedInstanceState != null) { 196 mCacheCleared = savedInstanceState.getBoolean(KEY_CACHE_CLEARED, false); 197 mDataCleared = savedInstanceState.getBoolean(KEY_DATA_CLEARED, false); 198 mCacheCleared = mCacheCleared || mDataCleared; 199 } 200 ConfirmationDialogFragment.resetListeners( 201 (ConfirmationDialogFragment) findDialogByTag(CONFIRM_CLEAR_STORAGE_DIALOG_TAG), 202 mConfirmClearStorageDialog, /* rejectListener= */ null); 203 ConfirmationDialogFragment.resetListeners( 204 (ConfirmationDialogFragment) findDialogByTag( 205 CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG), 206 mConfirmCannotClearStorageDialog, /* rejectListener= */ null); 207 } 208 209 @Override onActivityCreated(Bundle savedInstanceState)210 public void onActivityCreated(Bundle savedInstanceState) { 211 super.onActivityCreated(savedInstanceState); 212 213 mClearStorageButton = requireActivity().findViewById(R.id.action_button1); 214 mClearStorageButton.setVisibility(View.VISIBLE); 215 mClearStorageButton.setEnabled(false); 216 mClearStorageButton.setText(R.string.storage_clear_user_data_text); 217 218 mClearCacheButton = requireActivity().findViewById(R.id.action_button2); 219 mClearCacheButton.setVisibility(View.VISIBLE); 220 mClearCacheButton.setEnabled(false); 221 mClearCacheButton.setText(R.string.storage_clear_cache_btn_text); 222 } 223 224 @Override onResume()225 public void onResume() { 226 super.onResume(); 227 mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 228 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 229 mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( 230 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 231 updateSize(); 232 } 233 234 @Override onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared, boolean dataCleared)235 public void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared, 236 boolean dataCleared) { 237 if (data == null) { 238 mClearStorageButton.setEnabled(false); 239 mClearCacheButton.setEnabled(false); 240 } else { 241 long cacheSize = data.getCacheBytes(); 242 long dataSize = data.getDataBytes() - cacheSize; 243 244 if (dataSize <= 0 || !mCanClearData || mDataCleared) { 245 mClearStorageButton.setEnabled(false); 246 } else { 247 mClearStorageButton.setEnabled(true); 248 mClearStorageButton.setOnClickListener(v -> handleClearDataClick()); 249 } 250 if (cacheSize <= 0 || mCacheCleared) { 251 mClearCacheButton.setEnabled(false); 252 } else { 253 mClearCacheButton.setEnabled(true); 254 mClearCacheButton.setOnClickListener(v -> handleClearCacheClick()); 255 } 256 } 257 if (mAppsControlDisallowedBySystem) { 258 mClearStorageButton.setEnabled(false); 259 mClearCacheButton.setEnabled(false); 260 } 261 } 262 handleClearCacheClick()263 private void handleClearCacheClick() { 264 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 265 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 266 getActivity(), mAppsControlDisallowedAdmin); 267 return; 268 } 269 // Lazy initialization of observer. 270 if (mClearCacheObserver == null) { 271 mClearCacheObserver = new ClearCacheObserver(); 272 } 273 mPackageManager.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); 274 } 275 handleClearDataClick()276 private void handleClearDataClick() { 277 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 278 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 279 getActivity(), mAppsControlDisallowedAdmin); 280 } else if (mAppEntry.info.manageSpaceActivityName != null) { 281 Intent intent = new Intent(Intent.ACTION_DEFAULT); 282 intent.setClassName(mAppEntry.info.packageName, 283 mAppEntry.info.manageSpaceActivityName); 284 startActivityForResult(intent, REQUEST_MANAGE_SPACE); 285 } else { 286 showClearDataDialog(); 287 } 288 } 289 290 /* 291 * Private method to initiate clearing user data when the user clicks the clear data 292 * button for a system package 293 */ initiateClearUserData()294 private void initiateClearUserData() { 295 mClearStorageButton.setEnabled(false); 296 // Invoke uninstall or clear user data based on sysPackage 297 String packageName = mAppEntry.info.packageName; 298 LOG.i("Clearing user data for package : " + packageName); 299 if (mClearDataObserver == null) { 300 mClearDataObserver = new ClearUserDataObserver(); 301 } 302 ActivityManager am = (ActivityManager) 303 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 304 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 305 if (!res) { 306 // Clearing data failed for some obscure reason. Just log error for now 307 LOG.i("Couldn't clear application user data for package:" + packageName); 308 showCannotClearDataDialog(); 309 } 310 } 311 312 /* 313 * Private method to handle clear message notification from observer when 314 * the async operation from PackageManager is complete 315 */ processClearMsg(Message msg)316 private void processClearMsg(Message msg) { 317 int result = msg.arg1; 318 String packageName = mAppEntry.info.packageName; 319 if (result == OP_SUCCESSFUL) { 320 LOG.i("Cleared user data for package : " + packageName); 321 updateSize(); 322 } else { 323 mClearStorageButton.setEnabled(true); 324 } 325 } 326 updateSize()327 private void updateSize() { 328 PackageManager packageManager = getActivity().getPackageManager(); 329 try { 330 mInfo = packageManager.getApplicationInfo(mPackageName, 0); 331 } catch (PackageManager.NameNotFoundException e) { 332 LOG.e("Could not find package", e); 333 } 334 if (mInfo == null) { 335 return; 336 } 337 LoaderManager loaderManager = LoaderManager.getInstance(this); 338 mAppsStorageStatsManager.startLoading(loaderManager, mInfo, mUserId, mCacheCleared, 339 mDataCleared); 340 } 341 showClearDataDialog()342 private void showClearDataDialog() { 343 ConfirmationDialogFragment confirmClearStorageDialog = 344 new ConfirmationDialogFragment.Builder(getContext()) 345 .setTitle(R.string.storage_clear_user_data_text) 346 .setMessage(getString(R.string.storage_clear_data_dlg_text)) 347 .setPositiveButton(R.string.okay, mConfirmClearStorageDialog) 348 .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null) 349 .build(); 350 showDialog(confirmClearStorageDialog, CONFIRM_CLEAR_STORAGE_DIALOG_TAG); 351 } 352 showCannotClearDataDialog()353 private void showCannotClearDataDialog() { 354 ConfirmationDialogFragment dialogFragment = 355 new ConfirmationDialogFragment.Builder(getContext()) 356 .setTitle(R.string.storage_clear_data_dlg_title) 357 .setMessage(getString(R.string.storage_clear_failed_dlg_text)) 358 .setPositiveButton(R.string.okay, mConfirmCannotClearStorageDialog) 359 .build(); 360 showDialog(dialogFragment, CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG); 361 } 362 363 private final Handler mHandler = new Handler() { 364 public void handleMessage(Message msg) { 365 if (getView() == null) { 366 return; 367 } 368 switch (msg.what) { 369 case MSG_CLEAR_USER_DATA: 370 mDataCleared = true; 371 mCacheCleared = true; 372 processClearMsg(msg); 373 break; 374 case MSG_CLEAR_CACHE: 375 mCacheCleared = true; 376 // Refresh size info 377 updateSize(); 378 break; 379 } 380 } 381 }; 382 383 class ClearCacheObserver extends IPackageDataObserver.Stub { onRemoveCompleted(final String packageName, final boolean succeeded)384 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 385 Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE); 386 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 387 mHandler.sendMessage(msg); 388 } 389 } 390 391 class ClearUserDataObserver extends IPackageDataObserver.Stub { onRemoveCompleted(final String packageName, final boolean succeeded)392 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 393 Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA); 394 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 395 mHandler.sendMessage(msg); 396 } 397 } 398 } 399