AppStorageSettings.java revision 954d8dad5e395544f95ca211466bcc7195d31877
1/* 2 * Copyright (C) 2015 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 17package com.android.settings.applications; 18 19import android.app.ActivityManager; 20import android.app.AlertDialog; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.content.pm.ApplicationInfo; 25import android.content.pm.IPackageDataObserver; 26import android.os.Bundle; 27import android.os.Environment; 28import android.os.Handler; 29import android.os.Message; 30import android.os.storage.StorageManager; 31import android.os.storage.VolumeInfo; 32import android.text.format.Formatter; 33import android.util.Log; 34import android.view.View; 35import android.view.View.OnClickListener; 36import android.widget.Button; 37import android.widget.TextView; 38 39import com.android.internal.logging.MetricsLogger; 40import com.android.settings.DropDownPreference; 41import com.android.settings.R; 42import com.android.settings.Utils; 43import com.android.settings.applications.ApplicationsState.AppEntry; 44import com.android.settings.applications.ApplicationsState.Callbacks; 45import com.android.settings.deviceinfo.StorageWizardMoveConfirm; 46 47import java.util.Collections; 48import java.util.List; 49import java.util.Objects; 50 51public class AppStorageSettings extends AppInfoWithHeader 52 implements OnClickListener, Callbacks, DropDownPreference.Callback { 53 private static final String TAG = AppStorageSettings.class.getSimpleName(); 54 55 //internal constants used in Handler 56 private static final int OP_SUCCESSFUL = 1; 57 private static final int OP_FAILED = 2; 58 private static final int MSG_CLEAR_USER_DATA = 1; 59 private static final int MSG_CLEAR_CACHE = 3; 60 61 // invalid size value used initially and also when size retrieval through PackageManager 62 // fails for whatever reason 63 private static final int SIZE_INVALID = -1; 64 65 // Result code identifiers 66 public static final int REQUEST_MANAGE_SPACE = 2; 67 68 private static final int DLG_CLEAR_DATA = DLG_BASE + 1; 69 private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2; 70 71 private static final String KEY_MOVE_PREFERENCE = "app_location_setting"; 72 private static final String KEY_STORAGE_SETTINGS = "storage_settings"; 73 private static final String KEY_CACHE_SETTINGS = "cache_settings"; 74 75 private TextView mTotalSize; 76 private TextView mAppSize; 77 private TextView mDataSize; 78 private TextView mExternalCodeSize; 79 private TextView mExternalDataSize; 80 81 // Views related to cache info 82 private TextView mCacheSize; 83 private Button mClearDataButton; 84 private Button mClearCacheButton; 85 86 private DropDownPreference mMoveDropDown; 87 88 private boolean mCanClearData = true; 89 private boolean mHaveSizes = false; 90 91 private long mLastCodeSize = -1; 92 private long mLastDataSize = -1; 93 private long mLastExternalCodeSize = -1; 94 private long mLastExternalDataSize = -1; 95 private long mLastCacheSize = -1; 96 private long mLastTotalSize = -1; 97 98 private ClearCacheObserver mClearCacheObserver; 99 private ClearUserDataObserver mClearDataObserver; 100 101 // Resource strings 102 private CharSequence mInvalidSizeStr; 103 private CharSequence mComputingStr; 104 105 @Override 106 public void onCreate(Bundle savedInstanceState) { 107 super.onCreate(savedInstanceState); 108 109 addPreferencesFromResource(R.xml.app_storage_settings); 110 setupViews(); 111 } 112 113 private void setupViews() { 114 mComputingStr = getActivity().getText(R.string.computing_size); 115 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 116 LayoutPreference view = (LayoutPreference) findPreference(KEY_STORAGE_SETTINGS); 117 118 // Set default values on sizes 119 mTotalSize = (TextView) view.findViewById(R.id.total_size_text); 120 mAppSize = (TextView) view.findViewById(R.id.application_size_text); 121 mDataSize = (TextView) view.findViewById(R.id.data_size_text); 122 mExternalCodeSize = (TextView) view.findViewById(R.id.external_code_size_text); 123 mExternalDataSize = (TextView) view.findViewById(R.id.external_data_size_text); 124 125 if (Environment.isExternalStorageEmulated()) { 126 ((View) mExternalCodeSize.getParent()).setVisibility(View.GONE); 127 ((View) mExternalDataSize.getParent()).setVisibility(View.GONE); 128 } 129 mClearDataButton = (Button) view.findViewById(R.id.clear_data_button) 130 .findViewById(R.id.button); 131 132 mMoveDropDown = (DropDownPreference) findPreference(KEY_MOVE_PREFERENCE); 133 mMoveDropDown.setCallback(this); 134 135 view = (LayoutPreference) findPreference(KEY_CACHE_SETTINGS); 136 // Cache section 137 mCacheSize = (TextView) view.findViewById(R.id.cache_size_text); 138 mClearCacheButton = (Button) view.findViewById(R.id.clear_cache_button) 139 .findViewById(R.id.button); 140 mClearCacheButton.setText(R.string.clear_cache_btn_text); 141 } 142 143 @Override 144 public void onClick(View v) { 145 if (v == mClearCacheButton) { 146 // Lazy initialization of observer 147 if (mClearCacheObserver == null) { 148 mClearCacheObserver = new ClearCacheObserver(); 149 } 150 mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); 151 } else if(v == mClearDataButton) { 152 if (mAppEntry.info.manageSpaceActivityName != null) { 153 if (!Utils.isMonkeyRunning()) { 154 Intent intent = new Intent(Intent.ACTION_DEFAULT); 155 intent.setClassName(mAppEntry.info.packageName, 156 mAppEntry.info.manageSpaceActivityName); 157 startActivityForResult(intent, REQUEST_MANAGE_SPACE); 158 } 159 } else { 160 showDialogInner(DLG_CLEAR_DATA, 0); 161 } 162 } 163 } 164 165 @Override 166 public boolean onItemSelected(int pos, Object value) { 167 final Context context = getActivity(); 168 169 // If not current volume, kick off move wizard 170 final VolumeInfo targetVol = (VolumeInfo) value; 171 final VolumeInfo currentVol = context.getPackageManager().getPackageCurrentVolume( 172 mAppEntry.info); 173 if (!Objects.equals(targetVol, currentVol)) { 174 final Intent intent = new Intent(context, StorageWizardMoveConfirm.class); 175 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId()); 176 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); 177 startActivity(intent); 178 } 179 180 return true; 181 } 182 183 private String getSizeStr(long size) { 184 if (size == SIZE_INVALID) { 185 return mInvalidSizeStr.toString(); 186 } 187 return Formatter.formatFileSize(getActivity(), size); 188 } 189 190 private void refreshSizeInfo() { 191 if (mAppEntry.size == ApplicationsState.SIZE_INVALID 192 || mAppEntry.size == ApplicationsState.SIZE_UNKNOWN) { 193 mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1; 194 if (!mHaveSizes) { 195 mAppSize.setText(mComputingStr); 196 mDataSize.setText(mComputingStr); 197 mCacheSize.setText(mComputingStr); 198 mTotalSize.setText(mComputingStr); 199 } 200 mClearDataButton.setEnabled(false); 201 mClearCacheButton.setEnabled(false); 202 203 } else { 204 mHaveSizes = true; 205 long codeSize = mAppEntry.codeSize; 206 long dataSize = mAppEntry.dataSize; 207 if (Environment.isExternalStorageEmulated()) { 208 codeSize += mAppEntry.externalCodeSize; 209 dataSize += mAppEntry.externalDataSize; 210 } else { 211 if (mLastExternalCodeSize != mAppEntry.externalCodeSize) { 212 mLastExternalCodeSize = mAppEntry.externalCodeSize; 213 mExternalCodeSize.setText(getSizeStr(mAppEntry.externalCodeSize)); 214 } 215 if (mLastExternalDataSize != mAppEntry.externalDataSize) { 216 mLastExternalDataSize = mAppEntry.externalDataSize; 217 mExternalDataSize.setText(getSizeStr( mAppEntry.externalDataSize)); 218 } 219 } 220 if (mLastCodeSize != codeSize) { 221 mLastCodeSize = codeSize; 222 mAppSize.setText(getSizeStr(codeSize)); 223 } 224 if (mLastDataSize != dataSize) { 225 mLastDataSize = dataSize; 226 mDataSize.setText(getSizeStr(dataSize)); 227 } 228 long cacheSize = mAppEntry.cacheSize + mAppEntry.externalCacheSize; 229 if (mLastCacheSize != cacheSize) { 230 mLastCacheSize = cacheSize; 231 mCacheSize.setText(getSizeStr(cacheSize)); 232 } 233 if (mLastTotalSize != mAppEntry.size) { 234 mLastTotalSize = mAppEntry.size; 235 mTotalSize.setText(getSizeStr(mAppEntry.size)); 236 } 237 238 if ((mAppEntry.dataSize+ mAppEntry.externalDataSize) <= 0 || !mCanClearData) { 239 mClearDataButton.setEnabled(false); 240 } else { 241 mClearDataButton.setEnabled(true); 242 mClearDataButton.setOnClickListener(this); 243 } 244 if (cacheSize <= 0) { 245 mClearCacheButton.setEnabled(false); 246 } else { 247 mClearCacheButton.setEnabled(true); 248 mClearCacheButton.setOnClickListener(this); 249 } 250 } 251 if (mAppControlRestricted) { 252 mClearCacheButton.setEnabled(false); 253 mClearDataButton.setEnabled(false); 254 } 255 } 256 257 @Override 258 protected boolean refreshUi() { 259 retrieveAppEntry(); 260 refreshButtons(); 261 refreshSizeInfo(); 262 263 final VolumeInfo currentVol = getActivity().getPackageManager() 264 .getPackageCurrentVolume(mAppEntry.info); 265 mMoveDropDown.setSelectedValue(currentVol); 266 267 return true; 268 } 269 270 private void refreshButtons() { 271 initMoveDropDown(); 272 initDataButtons(); 273 } 274 275 private void initDataButtons() { 276 // If the app doesn't have its own space management UI 277 // And it's a system app that doesn't allow clearing user data or is an active admin 278 // Then disable the Clear Data button. 279 if (mAppEntry.info.manageSpaceActivityName == null 280 && ((mAppEntry.info.flags&(ApplicationInfo.FLAG_SYSTEM 281 | ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)) 282 == ApplicationInfo.FLAG_SYSTEM 283 || mDpm.packageHasActiveAdmins(mPackageName))) { 284 mClearDataButton.setText(R.string.clear_user_data_text); 285 mClearDataButton.setEnabled(false); 286 mCanClearData = false; 287 } else { 288 if (mAppEntry.info.manageSpaceActivityName != null) { 289 mClearDataButton.setText(R.string.manage_space_text); 290 } else { 291 mClearDataButton.setText(R.string.clear_user_data_text); 292 } 293 mClearDataButton.setOnClickListener(this); 294 } 295 296 if (mAppControlRestricted) { 297 mClearDataButton.setEnabled(false); 298 } 299 } 300 301 private void initMoveDropDown() { 302 final Context context = getActivity(); 303 final StorageManager storage = context.getSystemService(StorageManager.class); 304 305 final List<VolumeInfo> candidates = context.getPackageManager() 306 .getPackageCandidateVolumes(mAppEntry.info); 307 Collections.sort(candidates, VolumeInfo.getDescriptionComparator()); 308 309 mMoveDropDown.clearItems(); 310 for (VolumeInfo vol : candidates) { 311 final String volDescrip = storage.getBestVolumeDescription(vol); 312 mMoveDropDown.addItem(volDescrip, vol); 313 } 314 mMoveDropDown.setSelectable(!mAppControlRestricted); 315 } 316 317 /* 318 * Private method to initiate clearing user data when the user clicks the clear data 319 * button for a system package 320 */ 321 private void initiateClearUserData() { 322 mClearDataButton.setEnabled(false); 323 // Invoke uninstall or clear user data based on sysPackage 324 String packageName = mAppEntry.info.packageName; 325 Log.i(TAG, "Clearing user data for package : " + packageName); 326 if (mClearDataObserver == null) { 327 mClearDataObserver = new ClearUserDataObserver(); 328 } 329 ActivityManager am = (ActivityManager) 330 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 331 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 332 if (!res) { 333 // Clearing data failed for some obscure reason. Just log error for now 334 Log.i(TAG, "Couldnt clear application user data for package:"+packageName); 335 showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); 336 } else { 337 mClearDataButton.setText(R.string.recompute_size); 338 } 339 } 340 341 /* 342 * Private method to handle clear message notification from observer when 343 * the async operation from PackageManager is complete 344 */ 345 private void processClearMsg(Message msg) { 346 int result = msg.arg1; 347 String packageName = mAppEntry.info.packageName; 348 mClearDataButton.setText(R.string.clear_user_data_text); 349 if (result == OP_SUCCESSFUL) { 350 Log.i(TAG, "Cleared user data for package : "+packageName); 351 mState.requestSize(mPackageName, mUserId); 352 } else { 353 mClearDataButton.setEnabled(true); 354 } 355 } 356 357 @Override 358 protected AlertDialog createDialog(int id, int errorCode) { 359 switch (id) { 360 case DLG_CLEAR_DATA: 361 return new AlertDialog.Builder(getActivity()) 362 .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) 363 .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) 364 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 365 public void onClick(DialogInterface dialog, int which) { 366 // Clear user data here 367 initiateClearUserData(); 368 } 369 }) 370 .setNegativeButton(R.string.dlg_cancel, null) 371 .create(); 372 case DLG_CANNOT_CLEAR_DATA: 373 return new AlertDialog.Builder(getActivity()) 374 .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) 375 .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) 376 .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 377 public void onClick(DialogInterface dialog, int which) { 378 mClearDataButton.setEnabled(false); 379 //force to recompute changed value 380 setIntentAndFinish(false, false); 381 } 382 }) 383 .create(); 384 } 385 return null; 386 } 387 388 @Override 389 public void onPackageSizeChanged(String packageName) { 390 if (packageName.equals(mAppEntry.info.packageName)) { 391 refreshSizeInfo(); 392 } 393 } 394 395 private final Handler mHandler = new Handler() { 396 public void handleMessage(Message msg) { 397 if (getView() == null) { 398 return; 399 } 400 switch (msg.what) { 401 case MSG_CLEAR_USER_DATA: 402 processClearMsg(msg); 403 break; 404 case MSG_CLEAR_CACHE: 405 // Refresh size info 406 mState.requestSize(mPackageName, mUserId); 407 break; 408 } 409 } 410 }; 411 412 public static CharSequence getSummary(AppEntry appEntry, Context context) { 413 if (appEntry.size == ApplicationsState.SIZE_INVALID 414 || appEntry.size == ApplicationsState.SIZE_UNKNOWN) { 415 return context.getText(R.string.computing_size); 416 } else { 417 CharSequence storageType = context.getString( 418 (appEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 419 ? R.string.storage_type_external 420 : R.string.storage_type_internal); 421 return context.getString(R.string.storage_summary_format, 422 getSize(appEntry, context), storageType); 423 } 424 } 425 426 private static CharSequence getSize(AppEntry appEntry, Context context) { 427 long size = appEntry.size; 428 if (size == SIZE_INVALID) { 429 return context.getText(R.string.invalid_size_value); 430 } 431 return Formatter.formatFileSize(context, size); 432 } 433 434 @Override 435 protected int getMetricsCategory() { 436 return MetricsLogger.APPLICATIONS_APP_STORAGE; 437 } 438 439 class ClearCacheObserver extends IPackageDataObserver.Stub { 440 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 441 final Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE); 442 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 443 mHandler.sendMessage(msg); 444 } 445 } 446 447 class ClearUserDataObserver extends IPackageDataObserver.Stub { 448 public void onRemoveCompleted(final String packageName, final boolean succeeded) { 449 final Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA); 450 msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; 451 mHandler.sendMessage(msg); 452 } 453 } 454} 455