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