1 /* 2 * Copyright (C) 2020 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 static com.android.car.settings.common.ActionButtonsPreference.ActionButtons; 20 21 import android.app.ActivityManager; 22 import android.car.drivingstate.CarUxRestrictions; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.IPackageDataObserver; 27 import android.content.pm.PackageManager; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.UserHandle; 31 import android.os.UserManager; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.loader.app.LoaderManager; 35 36 import com.android.car.settings.R; 37 import com.android.car.settings.common.ActionButtonInfo; 38 import com.android.car.settings.common.ActionButtonsPreference; 39 import com.android.car.settings.common.ConfirmationDialogFragment; 40 import com.android.car.settings.common.FragmentController; 41 import com.android.car.settings.common.Logger; 42 import com.android.car.settings.common.PreferenceController; 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 /** 49 * Displays the action buttons to clear an applications cache and user data. 50 */ 51 public class StorageApplicationActionButtonsPreferenceController extends 52 PreferenceController<ActionButtonsPreference> implements 53 AppsStorageStatsManager.Callback { 54 private static final Logger LOG = new Logger( 55 StorageApplicationActionButtonsPreferenceController.class); 56 57 @VisibleForTesting 58 static final String CONFIRM_CLEAR_STORAGE_DIALOG_TAG = 59 "com.android.car.settings.storage.ConfirmClearStorageDialog"; 60 61 @VisibleForTesting 62 static final String CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG = 63 "com.android.car.settings.storage.ConfirmCannotClearStorageDialog"; 64 65 public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; 66 // Result code identifiers 67 public static final int REQUEST_MANAGE_SPACE = 2; 68 69 // Internal constants used in Handler 70 private static final int OP_SUCCESSFUL = 1; 71 private static final int OP_FAILED = 2; 72 73 // Constant used in handler to determine when the user data is cleared. 74 private static final int MSG_CLEAR_USER_DATA = 1; 75 // Constant used in handler to determine when the cache is cleared. 76 private static final int MSG_CLEAR_CACHE = 2; 77 78 private ActionButtonInfo mClearStorageButton; 79 private ActionButtonInfo mClearCacheButton; 80 81 private ApplicationsState.AppEntry mAppEntry; 82 private String mPackageName; 83 private ApplicationInfo mInfo; 84 private AppsStorageStatsManager mAppsStorageStatsManager; 85 private LoaderManager mLoaderManager; 86 87 // An observer callback to get notified when the cache file deletion is complete. 88 private ClearCacheObserver mClearCacheObserver; 89 // An observer callback to get notified when the user data deletion is complete. 90 private ClearUserDataObserver mClearDataObserver; 91 92 private PackageManager mPackageManager; 93 private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin; 94 private boolean mAppsControlDisallowedBySystem; 95 private int mUserId; 96 97 private boolean mCacheCleared; 98 private boolean mDataCleared; 99 100 private final ConfirmationDialogFragment.ConfirmListener mConfirmClearStorageDialog = 101 arguments -> initiateClearUserData(); 102 103 private final ConfirmationDialogFragment.ConfirmListener mConfirmCannotClearStorageDialog = 104 arguments -> mClearStorageButton.setEnabled(false); 105 StorageApplicationActionButtonsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)106 public StorageApplicationActionButtonsPreferenceController(Context context, 107 String preferenceKey, FragmentController fragmentController, 108 CarUxRestrictions uxRestrictions) { 109 super(context, preferenceKey, fragmentController, uxRestrictions); 110 mUserId = UserHandle.myUserId(); 111 mPackageManager = context.getPackageManager(); 112 } 113 114 @Override getPreferenceType()115 protected Class<ActionButtonsPreference> getPreferenceType() { 116 return ActionButtonsPreference.class; 117 } 118 119 /** Sets the {@link ApplicationsState.AppEntry} which is used to load the app name and icon. */ setAppEntry( ApplicationsState.AppEntry appEntry)120 public StorageApplicationActionButtonsPreferenceController setAppEntry( 121 ApplicationsState.AppEntry appEntry) { 122 mAppEntry = appEntry; 123 return this; 124 } 125 126 /** 127 * Set the packageName, which is used to perform actions on a particular package. 128 */ setPackageName(String packageName)129 public StorageApplicationActionButtonsPreferenceController setPackageName(String packageName) { 130 mPackageName = packageName; 131 return this; 132 } 133 134 /** 135 * Sets the {@link AppsStorageStatsManager} which will be used to register the controller to the 136 * Listener {@link AppsStorageStatsManager.Callback}. 137 */ setAppsStorageStatsManager( AppsStorageStatsManager appsStorageStatsManager)138 public StorageApplicationActionButtonsPreferenceController setAppsStorageStatsManager( 139 AppsStorageStatsManager appsStorageStatsManager) { 140 mAppsStorageStatsManager = appsStorageStatsManager; 141 return this; 142 } 143 144 /** 145 * Sets the {@link LoaderManager} used to load app storage stats. 146 */ setLoaderManager( LoaderManager loaderManager)147 public StorageApplicationActionButtonsPreferenceController setLoaderManager( 148 LoaderManager loaderManager) { 149 mLoaderManager = loaderManager; 150 return this; 151 } 152 153 @Override checkInitialized()154 protected void checkInitialized() { 155 if (mAppEntry == null || mPackageName == null || mAppsStorageStatsManager == null 156 || mLoaderManager == null) { 157 throw new IllegalStateException( 158 "AppEntry, PackageName, AppStorageStatsManager, and LoaderManager should be " 159 + "set before calling this function"); 160 } 161 } 162 163 @Override onCreateInternal()164 protected void onCreateInternal() { 165 mAppsStorageStatsManager.registerListener(this); 166 167 mClearStorageButton = getPreference().getButton(ActionButtons.BUTTON1); 168 mClearCacheButton = getPreference().getButton(ActionButtons.BUTTON2); 169 170 ConfirmationDialogFragment.resetListeners( 171 (ConfirmationDialogFragment) getFragmentController().findDialogByTag( 172 CONFIRM_CLEAR_STORAGE_DIALOG_TAG), 173 mConfirmClearStorageDialog, 174 /* rejectListener= */ null, 175 /* neutralListener= */ null); 176 ConfirmationDialogFragment.resetListeners( 177 (ConfirmationDialogFragment) getFragmentController().findDialogByTag( 178 CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG), 179 mConfirmCannotClearStorageDialog, 180 /* rejectListener= */ null, 181 /* neutralListener= */ null); 182 183 mClearStorageButton 184 .setText(R.string.storage_clear_user_data_text) 185 .setIcon(R.drawable.ic_delete) 186 .setOnClickListener(i -> handleClearDataClick()) 187 .setEnabled(false); 188 mClearCacheButton 189 .setText(R.string.storage_clear_cache_btn_text) 190 .setIcon(R.drawable.ic_delete) 191 .setOnClickListener(i -> handleClearCacheClick()) 192 .setEnabled(false); 193 } 194 195 @Override onStartInternal()196 protected void onStartInternal() { 197 mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 198 getContext(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 199 mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( 200 getContext(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 201 } 202 203 @Override updateState(ActionButtonsPreference preference)204 protected void updateState(ActionButtonsPreference preference) { 205 try { 206 mInfo = mPackageManager.getApplicationInfo(mPackageName, 0); 207 } catch (PackageManager.NameNotFoundException e) { 208 LOG.e("Could not find package", e); 209 } 210 if (mInfo == null) { 211 return; 212 } 213 mAppsStorageStatsManager.startLoading(mLoaderManager, mInfo, mUserId, mCacheCleared, 214 mDataCleared); 215 } 216 217 @Override onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared, boolean dataCleared)218 public void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared, 219 boolean dataCleared) { 220 if (data == null || mAppsControlDisallowedBySystem) { 221 mClearStorageButton.setEnabled(false); 222 mClearCacheButton.setEnabled(false); 223 } else { 224 long cacheSize = data.getCacheBytes(); 225 long dataSize = data.getDataBytes() - cacheSize; 226 227 mClearStorageButton.setEnabled(dataSize > 0 && !mDataCleared); 228 mClearCacheButton.setEnabled(cacheSize > 0 && !mCacheCleared); 229 } 230 } 231 232 @VisibleForTesting setPackageManager(PackageManager packageManager)233 void setPackageManager(PackageManager packageManager) { 234 mPackageManager = packageManager; 235 } 236 237 @VisibleForTesting setAppsControlDisallowedAdmin(RestrictedLockUtils.EnforcedAdmin admin)238 void setAppsControlDisallowedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { 239 mAppsControlDisallowedAdmin = admin; 240 } 241 242 @VisibleForTesting setAppsControlDisallowedBySystem(boolean disallowed)243 void setAppsControlDisallowedBySystem(boolean disallowed) { 244 mAppsControlDisallowedBySystem = disallowed; 245 } 246 handleClearCacheClick()247 private void handleClearCacheClick() { 248 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 249 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 250 getContext(), mAppsControlDisallowedAdmin); 251 return; 252 } 253 // Lazy initialization of observer. 254 if (mClearCacheObserver == null) { 255 mClearCacheObserver = new ClearCacheObserver(); 256 } 257 mPackageManager.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); 258 } 259 handleClearDataClick()260 private void handleClearDataClick() { 261 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 262 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 263 getContext(), mAppsControlDisallowedAdmin); 264 } else { 265 Intent intent = new Intent(Intent.ACTION_DEFAULT); 266 boolean isManageSpaceActivityAvailable = false; 267 if (mAppEntry.info.manageSpaceActivityName != null) { 268 intent.setClassName(mAppEntry.info.packageName, 269 mAppEntry.info.manageSpaceActivityName); 270 isManageSpaceActivityAvailable = mPackageManager.resolveActivity( 271 intent, /* flags= */ 0) != null; 272 } 273 274 if (isManageSpaceActivityAvailable) { 275 getFragmentController().startActivityForResult(intent, 276 REQUEST_MANAGE_SPACE, /* callback= */ null); 277 } else { 278 showClearDataDialog(); 279 } 280 } 281 } 282 283 /* 284 * Private method to initiate clearing user data when the user clicks the clear data 285 * button for a system package 286 */ initiateClearUserData()287 private void initiateClearUserData() { 288 mClearStorageButton.setEnabled(false); 289 // Invoke uninstall or clear user data based on sysPackage 290 String packageName = mAppEntry.info.packageName; 291 LOG.i("Clearing user data for package : " + packageName); 292 if (mClearDataObserver == null) { 293 mClearDataObserver = new ClearUserDataObserver(); 294 } 295 ActivityManager am = (ActivityManager) 296 getContext().getSystemService(Context.ACTIVITY_SERVICE); 297 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 298 if (!res) { 299 // Clearing data failed for some obscure reason. Just log error for now 300 LOG.i("Couldn't clear application user data for package:" + packageName); 301 showCannotClearDataDialog(); 302 } 303 } 304 305 /* 306 * Private method to handle clear message notification from observer when 307 * the async operation from PackageManager is complete 308 */ processClearMsg(Message msg)309 private void processClearMsg(Message msg) { 310 int result = msg.arg1; 311 String packageName = mAppEntry.info.packageName; 312 if (result == OP_SUCCESSFUL) { 313 LOG.i("Cleared user data for package : " + packageName); 314 refreshUi(); 315 } else { 316 mClearStorageButton.setEnabled(true); 317 } 318 } 319 showClearDataDialog()320 private void showClearDataDialog() { 321 ConfirmationDialogFragment confirmClearStorageDialog = 322 new ConfirmationDialogFragment.Builder(getContext()) 323 .setTitle(R.string.storage_clear_user_data_text) 324 .setMessage(getContext().getString(R.string.storage_clear_data_dlg_text)) 325 .setPositiveButton(R.string.okay, mConfirmClearStorageDialog) 326 .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null) 327 .build(); 328 getFragmentController().showDialog(confirmClearStorageDialog, 329 CONFIRM_CLEAR_STORAGE_DIALOG_TAG); 330 } 331 showCannotClearDataDialog()332 private void showCannotClearDataDialog() { 333 ConfirmationDialogFragment dialogFragment = 334 new ConfirmationDialogFragment.Builder(getContext()) 335 .setTitle(R.string.storage_clear_data_dlg_title) 336 .setMessage(getContext().getString(R.string.storage_clear_failed_dlg_text)) 337 .setPositiveButton(R.string.okay, mConfirmCannotClearStorageDialog) 338 .build(); 339 getFragmentController().showDialog(dialogFragment, CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG); 340 } 341 342 private final Handler mHandler = new Handler() { 343 public void handleMessage(Message msg) { 344 switch (msg.what) { 345 case MSG_CLEAR_USER_DATA: 346 mDataCleared = true; 347 mCacheCleared = true; 348 processClearMsg(msg); 349 break; 350 case MSG_CLEAR_CACHE: 351 mCacheCleared = true; 352 // Refresh info 353 refreshUi(); 354 break; 355 } 356 } 357 }; 358 359 class ClearCacheObserver extends IPackageDataObserver.Stub { onRemoveCompleted(final String packageName, final boolean succeeded)360 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 361 Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE); 362 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 363 mHandler.sendMessage(msg); 364 } 365 } 366 367 class ClearUserDataObserver extends IPackageDataObserver.Stub { onRemoveCompleted(final String packageName, final boolean succeeded)368 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 369 Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA); 370 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 371 mHandler.sendMessage(msg); 372 } 373 } 374 } 375