OSDN Git Service

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