diff options
Diffstat (limited to 'src/com/android/settings/applications/AppStorageSettings.java')
-rw-r--r-- | src/com/android/settings/applications/AppStorageSettings.java | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java new file mode 100644 index 0000000..5ae5e8f --- /dev/null +++ b/src/com/android/settings/applications/AppStorageSettings.java @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications; + +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageMoveObserver; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.text.format.Formatter; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.applications.ApplicationsState.AppEntry; +import com.android.settings.applications.ApplicationsState.Callbacks; + +public class AppStorageSettings extends AppInfoWithHeader implements OnClickListener, Callbacks { + private static final String TAG = "AppStorageSettings"; + + //internal constants used in Handler + private static final int OP_SUCCESSFUL = 1; + private static final int OP_FAILED = 2; + private static final int MSG_CLEAR_USER_DATA = 1; + private static final int MSG_CLEAR_CACHE = 3; + private static final int MSG_PACKAGE_MOVE = 4; + + // invalid size value used initially and also when size retrieval through PackageManager + // fails for whatever reason + private static final int SIZE_INVALID = -1; + + // Result code identifiers + public static final int REQUEST_MANAGE_SPACE = 2; + + private static final int DLG_CLEAR_DATA = DLG_BASE + 1; + private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2; + private static final int DLG_MOVE_FAILED = DLG_BASE + 3; + + private CanBeOnSdCardChecker mCanBeOnSdCardChecker; + private TextView mTotalSize; + private TextView mAppSize; + private TextView mDataSize; + private TextView mExternalCodeSize; + private TextView mExternalDataSize; + + // Views related to cache info + private TextView mCacheSize; + private Button mClearDataButton; + private Button mClearCacheButton; + private Button mMoveAppButton; + private boolean mMoveInProgress = false; + + private boolean mCanClearData = true; + private boolean mHaveSizes = false; + + private long mLastCodeSize = -1; + private long mLastDataSize = -1; + private long mLastExternalCodeSize = -1; + private long mLastExternalDataSize = -1; + private long mLastCacheSize = -1; + private long mLastTotalSize = -1; + + private ClearCacheObserver mClearCacheObserver; + private ClearUserDataObserver mClearDataObserver; + private PackageMoveObserver mPackageMoveObserver; + + // Resource strings + private CharSequence mInvalidSizeStr; + private CharSequence mComputingStr; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mCanBeOnSdCardChecker = new CanBeOnSdCardChecker(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.storage_settings, container, false); + + final ViewGroup allDetails = (ViewGroup)view.findViewById(R.id.all_details); + Utils.forceCustomPadding(allDetails, true /* additive padding */); + mComputingStr = getActivity().getText(R.string.computing_size); + mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); + + // Set default values on sizes + mTotalSize = (TextView)view.findViewById(R.id.total_size_text); + mAppSize = (TextView)view.findViewById(R.id.application_size_text); + mDataSize = (TextView)view.findViewById(R.id.data_size_text); + mExternalCodeSize = (TextView)view.findViewById(R.id.external_code_size_text); + mExternalDataSize = (TextView)view.findViewById(R.id.external_data_size_text); + + if (Environment.isExternalStorageEmulated()) { + ((View)mExternalCodeSize.getParent()).setVisibility(View.GONE); + ((View)mExternalDataSize.getParent()).setVisibility(View.GONE); + } + + // Initialize clear data and move install location buttons + View data_buttons_panel = view.findViewById(R.id.data_buttons_panel); + mMoveAppButton = (Button) data_buttons_panel.findViewById(R.id.left_button); + + // Cache section + mCacheSize = (TextView)view.findViewById(R.id.cache_size_text); + mClearCacheButton = (Button)view.findViewById(R.id.clear_cache_button); + mClearDataButton = (Button) data_buttons_panel.findViewById(R.id.right_button); + + return view; + } + + @Override + public void onClick(View v) { + if (v == mClearCacheButton) { + // Lazy initialization of observer + if (mClearCacheObserver == null) { + mClearCacheObserver = new ClearCacheObserver(); + } + mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver); + } else if(v == mClearDataButton) { + if (mAppEntry.info.manageSpaceActivityName != null) { + if (!Utils.isMonkeyRunning()) { + Intent intent = new Intent(Intent.ACTION_DEFAULT); + intent.setClassName(mAppEntry.info.packageName, + mAppEntry.info.manageSpaceActivityName); + startActivityForResult(intent, REQUEST_MANAGE_SPACE); + } + } else { + showDialogInner(DLG_CLEAR_DATA, 0); + } + } else if (v == mMoveAppButton) { + if (mPackageMoveObserver == null) { + mPackageMoveObserver = new PackageMoveObserver(); + } + int moveFlags = (mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 ? + PackageManager.MOVE_INTERNAL : PackageManager.MOVE_EXTERNAL_MEDIA; + mMoveInProgress = true; + refreshButtons(); + mPm.movePackage(mAppEntry.info.packageName, mPackageMoveObserver, moveFlags); + } + } + + private String getSizeStr(long size) { + if (size == SIZE_INVALID) { + return mInvalidSizeStr.toString(); + } + return Formatter.formatFileSize(getActivity(), size); + } + + private void refreshSizeInfo() { + if (mAppEntry.size == ApplicationsState.SIZE_INVALID + || mAppEntry.size == ApplicationsState.SIZE_UNKNOWN) { + mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1; + if (!mHaveSizes) { + mAppSize.setText(mComputingStr); + mDataSize.setText(mComputingStr); + mCacheSize.setText(mComputingStr); + mTotalSize.setText(mComputingStr); + } + mClearDataButton.setEnabled(false); + mClearCacheButton.setEnabled(false); + + } else { + mHaveSizes = true; + long codeSize = mAppEntry.codeSize; + long dataSize = mAppEntry.dataSize; + if (Environment.isExternalStorageEmulated()) { + codeSize += mAppEntry.externalCodeSize; + dataSize += mAppEntry.externalDataSize; + } else { + if (mLastExternalCodeSize != mAppEntry.externalCodeSize) { + mLastExternalCodeSize = mAppEntry.externalCodeSize; + mExternalCodeSize.setText(getSizeStr(mAppEntry.externalCodeSize)); + } + if (mLastExternalDataSize != mAppEntry.externalDataSize) { + mLastExternalDataSize = mAppEntry.externalDataSize; + mExternalDataSize.setText(getSizeStr( mAppEntry.externalDataSize)); + } + } + if (mLastCodeSize != codeSize) { + mLastCodeSize = codeSize; + mAppSize.setText(getSizeStr(codeSize)); + } + if (mLastDataSize != dataSize) { + mLastDataSize = dataSize; + mDataSize.setText(getSizeStr(dataSize)); + } + long cacheSize = mAppEntry.cacheSize + mAppEntry.externalCacheSize; + if (mLastCacheSize != cacheSize) { + mLastCacheSize = cacheSize; + mCacheSize.setText(getSizeStr(cacheSize)); + } + if (mLastTotalSize != mAppEntry.size) { + mLastTotalSize = mAppEntry.size; + mTotalSize.setText(getSizeStr(mAppEntry.size)); + } + + if ((mAppEntry.dataSize+ mAppEntry.externalDataSize) <= 0 || !mCanClearData) { + mClearDataButton.setEnabled(false); + } else { + mClearDataButton.setEnabled(true); + mClearDataButton.setOnClickListener(this); + } + if (cacheSize <= 0) { + mClearCacheButton.setEnabled(false); + } else { + mClearCacheButton.setEnabled(true); + mClearCacheButton.setOnClickListener(this); + } + } + if (mAppControlRestricted) { + mClearCacheButton.setEnabled(false); + mClearDataButton.setEnabled(false); + } + } + + @Override + protected boolean refreshUi() { + retrieveAppEntry(); + refreshButtons(); + refreshSizeInfo(); + return true; + } + + private void refreshButtons() { + if (!mMoveInProgress) { + initMoveButton(); + initDataButtons(); + } else { + mMoveAppButton.setText(R.string.moving); + mMoveAppButton.setEnabled(false); + } + } + + private void initDataButtons() { + // If the app doesn't have its own space management UI + // And it's a system app that doesn't allow clearing user data or is an active admin + // Then disable the Clear Data button. + if (mAppEntry.info.manageSpaceActivityName == null + && ((mAppEntry.info.flags&(ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)) + == ApplicationInfo.FLAG_SYSTEM + || mDpm.packageHasActiveAdmins(mPackageName))) { + mClearDataButton.setText(R.string.clear_user_data_text); + mClearDataButton.setEnabled(false); + mCanClearData = false; + } else { + if (mAppEntry.info.manageSpaceActivityName != null) { + mClearDataButton.setText(R.string.manage_space_text); + } else { + mClearDataButton.setText(R.string.clear_user_data_text); + } + mClearDataButton.setOnClickListener(this); + } + + if (mAppControlRestricted) { + mClearDataButton.setEnabled(false); + } + } + + private void initMoveButton() { + if (Environment.isExternalStorageEmulated()) { + mMoveAppButton.setVisibility(View.INVISIBLE); + return; + } + boolean dataOnly = (mPackageInfo == null) && (mAppEntry != null); + boolean moveDisable = true; + if (dataOnly) { + mMoveAppButton.setText(R.string.move_app); + } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + mMoveAppButton.setText(R.string.move_app_to_internal); + // Always let apps move to internal storage from sdcard. + moveDisable = false; + } else { + mMoveAppButton.setText(R.string.move_app_to_sdcard); + mCanBeOnSdCardChecker.init(); + moveDisable = !mCanBeOnSdCardChecker.check(mAppEntry.info); + } + if (moveDisable || mAppControlRestricted) { + mMoveAppButton.setEnabled(false); + } else { + mMoveAppButton.setOnClickListener(this); + mMoveAppButton.setEnabled(true); + } + } + + /* + * Private method to initiate clearing user data when the user clicks the clear data + * button for a system package + */ + private void initiateClearUserData() { + mClearDataButton.setEnabled(false); + // Invoke uninstall or clear user data based on sysPackage + String packageName = mAppEntry.info.packageName; + Log.i(TAG, "Clearing user data for package : " + packageName); + if (mClearDataObserver == null) { + mClearDataObserver = new ClearUserDataObserver(); + } + ActivityManager am = (ActivityManager) + getActivity().getSystemService(Context.ACTIVITY_SERVICE); + boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); + if (!res) { + // Clearing data failed for some obscure reason. Just log error for now + Log.i(TAG, "Couldnt clear application user data for package:"+packageName); + showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); + } else { + mClearDataButton.setText(R.string.recompute_size); + } + } + + private void processMoveMsg(Message msg) { + int result = msg.arg1; + String packageName = mAppEntry.info.packageName; + // Refresh the button attributes. + mMoveInProgress = false; + if (result == PackageManager.MOVE_SUCCEEDED) { + Log.i(TAG, "Moved resources for " + packageName); + // Refresh size information again. + mState.requestSize(mAppEntry.info.packageName); + } else { + showDialogInner(DLG_MOVE_FAILED, result); + } + refreshUi(); + } + + /* + * Private method to handle clear message notification from observer when + * the async operation from PackageManager is complete + */ + private void processClearMsg(Message msg) { + int result = msg.arg1; + String packageName = mAppEntry.info.packageName; + mClearDataButton.setText(R.string.clear_user_data_text); + if(result == OP_SUCCESSFUL) { + Log.i(TAG, "Cleared user data for package : "+packageName); + mState.requestSize(mAppEntry.info.packageName); + } else { + mClearDataButton.setEnabled(true); + } + } + + private CharSequence getMoveErrMsg(int errCode) { + switch (errCode) { + case PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE: + return getActivity().getString(R.string.insufficient_storage); + case PackageManager.MOVE_FAILED_DOESNT_EXIST: + return getActivity().getString(R.string.does_not_exist); + case PackageManager.MOVE_FAILED_FORWARD_LOCKED: + return getActivity().getString(R.string.app_forward_locked); + case PackageManager.MOVE_FAILED_INVALID_LOCATION: + return getActivity().getString(R.string.invalid_location); + case PackageManager.MOVE_FAILED_SYSTEM_PACKAGE: + return getActivity().getString(R.string.system_package); + case PackageManager.MOVE_FAILED_INTERNAL_ERROR: + return ""; + } + return ""; + } + + @Override + protected AlertDialog createDialog(int id, int errorCode) { + switch (id) { + case DLG_CLEAR_DATA: + return new AlertDialog.Builder(getActivity()) + .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) + .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) + .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Clear user data here + initiateClearUserData(); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) + .create(); + case DLG_CANNOT_CLEAR_DATA: + return new AlertDialog.Builder(getActivity()) + .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) + .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) + .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mClearDataButton.setEnabled(false); + //force to recompute changed value + setIntentAndFinish(false, false); + } + }) + .create(); + case DLG_MOVE_FAILED: + CharSequence msg = getActivity().getString(R.string.move_app_failed_dlg_text, + getMoveErrMsg(errorCode)); + return new AlertDialog.Builder(getActivity()) + .setTitle(getActivity().getText(R.string.move_app_failed_dlg_title)) + .setMessage(msg) + .setNeutralButton(R.string.dlg_ok, null) + .create(); + } + return null; + } + + @Override + public void onPackageSizeChanged(String packageName) { + if (packageName.equals(mAppEntry.info.packageName)) { + refreshSizeInfo(); + } + } + + private final Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + if (getView() == null) { + return; + } + switch (msg.what) { + case MSG_CLEAR_USER_DATA: + processClearMsg(msg); + break; + case MSG_CLEAR_CACHE: + // Refresh size info + mState.requestSize(mPackageName); + break; + case MSG_PACKAGE_MOVE: + processMoveMsg(msg); + break; + } + } + }; + + public static CharSequence getSummary(AppEntry appEntry, Context context) { + if (appEntry.size == ApplicationsState.SIZE_INVALID + || appEntry.size == ApplicationsState.SIZE_UNKNOWN) { + return context.getText(R.string.computing_size); + } else { + CharSequence storageType = context.getString( + (appEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 + ? R.string.storage_type_external + : R.string.storage_type_internal); + return context.getString(R.string.storage_summary_format, + getSize(appEntry, context), storageType); + } + } + + private static CharSequence getSize(AppEntry appEntry, Context context) { + long size = appEntry.size; + if (size == SIZE_INVALID) { + return context.getText(R.string.invalid_size_value); + } + return Formatter.formatFileSize(context, size); + } + + class ClearCacheObserver extends IPackageDataObserver.Stub { + public void onRemoveCompleted(final String packageName, final boolean succeeded) { + final Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE); + msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED; + mHandler.sendMessage(msg); + } + } + + class ClearUserDataObserver extends IPackageDataObserver.Stub { + public void onRemoveCompleted(final String packageName, final boolean succeeded) { + final Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA); + msg.arg1 = succeeded?OP_SUCCESSFUL:OP_FAILED; + mHandler.sendMessage(msg); + } + } + + class PackageMoveObserver extends IPackageMoveObserver.Stub { + public void packageMoved(String packageName, int returnCode) throws RemoteException { + final Message msg = mHandler.obtainMessage(MSG_PACKAGE_MOVE); + msg.arg1 = returnCode; + mHandler.sendMessage(msg); + } + } + +} |