• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 android.app.ActivityManager;
20 import android.car.userlib.CarUserManagerHelper;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.IPackageDataObserver;
25 import android.content.pm.PackageManager;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.os.UserHandle;
30 import android.os.UserManager;
31 import android.view.View;
32 import android.widget.Button;
33 
34 import androidx.annotation.LayoutRes;
35 import androidx.annotation.VisibleForTesting;
36 import androidx.annotation.XmlRes;
37 import androidx.loader.app.LoaderManager;
38 
39 import com.android.car.settings.R;
40 import com.android.car.settings.common.ConfirmationDialogFragment;
41 import com.android.car.settings.common.Logger;
42 import com.android.car.settings.common.SettingsFragment;
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 import java.util.Arrays;
49 import java.util.List;
50 
51 /**
52  * Fragment to display the applications storage information. Also provide buttons to clear the
53  * applications cache data and user data.
54  */
55 public class AppStorageSettingsDetailsFragment extends SettingsFragment implements
56         AppsStorageStatsManager.Callback {
57     private static final Logger LOG = new Logger(AppStorageSettingsDetailsFragment.class);
58 
59     @VisibleForTesting
60     static final String CONFIRM_CLEAR_STORAGE_DIALOG_TAG =
61             "com.android.car.settings.storage.ConfirmClearStorageDialog";
62 
63     @VisibleForTesting
64     static final String CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG =
65             "com.android.car.settings.storage.ConfirmCannotClearStorageDialog";
66 
67     public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
68     // Result code identifiers
69     public static final int REQUEST_MANAGE_SPACE = 2;
70 
71     // Internal constants used in Handler
72     private static final int OP_SUCCESSFUL = 1;
73     private static final int OP_FAILED = 2;
74 
75     // Constant used in handler to determine when the user data is cleared.
76     private static final int MSG_CLEAR_USER_DATA = 1;
77     // Constant used in handler to determine when the cache is cleared.
78     private static final int MSG_CLEAR_CACHE = 2;
79 
80     // Keys to save the instance values.
81     private static final String KEY_CACHE_CLEARED = "cache_cleared";
82     private static final String KEY_DATA_CLEARED = "data_cleared";
83 
84     // Package information
85     protected PackageManager mPackageManager;
86     private String mPackageName;
87 
88     // Application state info
89     private ApplicationsState.AppEntry mAppEntry;
90     private ApplicationsState mAppState;
91     private ApplicationInfo mInfo;
92     private AppsStorageStatsManager mAppsStorageStatsManager;
93 
94     // User info
95     private int mUserId;
96     private CarUserManagerHelper mCarUserManagerHelper;
97 
98     //  An observer callback to get notified when the cache file deletion is complete.
99     private ClearCacheObserver mClearCacheObserver;
100     //  An observer callback to get notified when the user data deletion is complete.
101     private ClearUserDataObserver mClearDataObserver;
102 
103     // The restriction enforced by admin.
104     private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin;
105     private boolean mAppsControlDisallowedBySystem;
106 
107     // Clear user data and cache buttons and state.
108     private Button mClearStorageButton;
109     private Button mClearCacheButton;
110     private boolean mCanClearData = true;
111     private boolean mCacheCleared;
112     private boolean mDataCleared;
113 
114     private final ConfirmationDialogFragment.ConfirmListener mConfirmClearStorageDialog =
115             arguments -> initiateClearUserData();
116 
117 
118     private final ConfirmationDialogFragment.ConfirmListener mConfirmCannotClearStorageDialog =
119             arguments -> mClearStorageButton.setEnabled(false);
120 
121     /** Creates an instance of this fragment, passing packageName as an argument. */
getInstance(String packageName)122     public static AppStorageSettingsDetailsFragment getInstance(String packageName) {
123         AppStorageSettingsDetailsFragment applicationDetailFragment =
124                 new AppStorageSettingsDetailsFragment();
125         Bundle bundle = new Bundle();
126         bundle.putString(EXTRA_PACKAGE_NAME, packageName);
127         applicationDetailFragment.setArguments(bundle);
128         return applicationDetailFragment;
129     }
130 
131     @Override
132     @XmlRes
getPreferenceScreenResId()133     protected int getPreferenceScreenResId() {
134         return R.xml.app_storage_settings_details_fragment;
135     }
136 
137     @Override
138     @LayoutRes
getActionBarLayoutId()139     protected int getActionBarLayoutId() {
140         return R.layout.action_bar_with_button;
141     }
142 
143     @Override
onAttach(Context context)144     public void onAttach(Context context) {
145         super.onAttach(context);
146         mCarUserManagerHelper = new CarUserManagerHelper(context);
147         mUserId = mCarUserManagerHelper.getCurrentProcessUserId();
148         mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME);
149         mAppState = ApplicationsState.getInstance(requireActivity().getApplication());
150         mAppEntry = mAppState.getEntry(mPackageName, mUserId);
151         StorageStatsSource storageStatsSource = new StorageStatsSource(context);
152         StorageStatsSource.AppStorageStats stats = null;
153         mPackageManager = context.getPackageManager();
154         try {
155             stats = storageStatsSource.getStatsForPackage(/* volumeUuid= */ null, mPackageName,
156                     UserHandle.of(mUserId));
157         } catch (Exception e) {
158             // This may happen if the package was removed during our calculation.
159             LOG.w("App unexpectedly not found", e);
160         }
161         mAppsStorageStatsManager = new AppsStorageStatsManager(context);
162         mAppsStorageStatsManager.registerListener(this);
163         use(StorageApplicationPreferenceController.class,
164                 R.string.pk_storage_application_details)
165                 .setAppEntry(mAppEntry)
166                 .setAppState(mAppState);
167 
168         List<StorageSizeBasePreferenceController> preferenceControllers = Arrays.asList(
169                 use(StorageApplicationSizePreferenceController.class,
170                         R.string.pk_storage_application_size),
171                 use(StorageApplicationTotalSizePreferenceController.class,
172                         R.string.pk_storage_application_total_size),
173                 use(StorageApplicationUserDataPreferenceController.class,
174                         R.string.pk_storage_application_data_size),
175                 use(StorageApplicationCacheSizePreferenceController.class,
176                         R.string.pk_storage_application_cache_size)
177         );
178 
179         for (StorageSizeBasePreferenceController pc : preferenceControllers) {
180             pc.setAppsStorageStatsManager(mAppsStorageStatsManager);
181             pc.setAppStorageStats(stats);
182         }
183     }
184 
185     @Override
onSaveInstanceState(Bundle outState)186     public void onSaveInstanceState(Bundle outState) {
187         super.onSaveInstanceState(outState);
188         outState.putBoolean(KEY_CACHE_CLEARED, mCacheCleared);
189         outState.putBoolean(KEY_DATA_CLEARED, mDataCleared);
190     }
191 
192     @Override
onCreate(Bundle savedInstanceState)193     public void onCreate(Bundle savedInstanceState) {
194         super.onCreate(savedInstanceState);
195         if (savedInstanceState != null) {
196             mCacheCleared = savedInstanceState.getBoolean(KEY_CACHE_CLEARED, false);
197             mDataCleared = savedInstanceState.getBoolean(KEY_DATA_CLEARED, false);
198             mCacheCleared = mCacheCleared || mDataCleared;
199         }
200         ConfirmationDialogFragment.resetListeners(
201                 (ConfirmationDialogFragment) findDialogByTag(CONFIRM_CLEAR_STORAGE_DIALOG_TAG),
202                 mConfirmClearStorageDialog, /* rejectListener= */ null);
203         ConfirmationDialogFragment.resetListeners(
204                 (ConfirmationDialogFragment) findDialogByTag(
205                         CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG),
206                 mConfirmCannotClearStorageDialog, /* rejectListener= */ null);
207     }
208 
209     @Override
onActivityCreated(Bundle savedInstanceState)210     public void onActivityCreated(Bundle savedInstanceState) {
211         super.onActivityCreated(savedInstanceState);
212 
213         mClearStorageButton = requireActivity().findViewById(R.id.action_button1);
214         mClearStorageButton.setVisibility(View.VISIBLE);
215         mClearStorageButton.setEnabled(false);
216         mClearStorageButton.setText(R.string.storage_clear_user_data_text);
217 
218         mClearCacheButton = requireActivity().findViewById(R.id.action_button2);
219         mClearCacheButton.setVisibility(View.VISIBLE);
220         mClearCacheButton.setEnabled(false);
221         mClearCacheButton.setText(R.string.storage_clear_cache_btn_text);
222     }
223 
224     @Override
onResume()225     public void onResume() {
226         super.onResume();
227         mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
228                 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId);
229         mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction(
230                 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId);
231         updateSize();
232     }
233 
234     @Override
onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared, boolean dataCleared)235     public void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared,
236             boolean dataCleared) {
237         if (data == null) {
238             mClearStorageButton.setEnabled(false);
239             mClearCacheButton.setEnabled(false);
240         } else {
241             long cacheSize = data.getCacheBytes();
242             long dataSize = data.getDataBytes() - cacheSize;
243 
244             if (dataSize <= 0 || !mCanClearData || mDataCleared) {
245                 mClearStorageButton.setEnabled(false);
246             } else {
247                 mClearStorageButton.setEnabled(true);
248                 mClearStorageButton.setOnClickListener(v -> handleClearDataClick());
249             }
250             if (cacheSize <= 0 || mCacheCleared) {
251                 mClearCacheButton.setEnabled(false);
252             } else {
253                 mClearCacheButton.setEnabled(true);
254                 mClearCacheButton.setOnClickListener(v -> handleClearCacheClick());
255             }
256         }
257         if (mAppsControlDisallowedBySystem) {
258             mClearStorageButton.setEnabled(false);
259             mClearCacheButton.setEnabled(false);
260         }
261     }
262 
handleClearCacheClick()263     private void handleClearCacheClick() {
264         if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
265             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
266                     getActivity(), mAppsControlDisallowedAdmin);
267             return;
268         }
269         // Lazy initialization of observer.
270         if (mClearCacheObserver == null) {
271             mClearCacheObserver = new ClearCacheObserver();
272         }
273         mPackageManager.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver);
274     }
275 
handleClearDataClick()276     private void handleClearDataClick() {
277         if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
278             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
279                     getActivity(), mAppsControlDisallowedAdmin);
280         } else if (mAppEntry.info.manageSpaceActivityName != null) {
281             Intent intent = new Intent(Intent.ACTION_DEFAULT);
282             intent.setClassName(mAppEntry.info.packageName,
283                     mAppEntry.info.manageSpaceActivityName);
284             startActivityForResult(intent, REQUEST_MANAGE_SPACE);
285         } else {
286             showClearDataDialog();
287         }
288     }
289 
290     /*
291      * Private method to initiate clearing user data when the user clicks the clear data
292      * button for a system package
293      */
initiateClearUserData()294     private void initiateClearUserData() {
295         mClearStorageButton.setEnabled(false);
296         // Invoke uninstall or clear user data based on sysPackage
297         String packageName = mAppEntry.info.packageName;
298         LOG.i("Clearing user data for package : " + packageName);
299         if (mClearDataObserver == null) {
300             mClearDataObserver = new ClearUserDataObserver();
301         }
302         ActivityManager am = (ActivityManager)
303                 getActivity().getSystemService(Context.ACTIVITY_SERVICE);
304         boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
305         if (!res) {
306             // Clearing data failed for some obscure reason. Just log error for now
307             LOG.i("Couldn't clear application user data for package:" + packageName);
308             showCannotClearDataDialog();
309         }
310     }
311 
312     /*
313      * Private method to handle clear message notification from observer when
314      * the async operation from PackageManager is complete
315      */
processClearMsg(Message msg)316     private void processClearMsg(Message msg) {
317         int result = msg.arg1;
318         String packageName = mAppEntry.info.packageName;
319         if (result == OP_SUCCESSFUL) {
320             LOG.i("Cleared user data for package : " + packageName);
321             updateSize();
322         } else {
323             mClearStorageButton.setEnabled(true);
324         }
325     }
326 
updateSize()327     private void updateSize() {
328         PackageManager packageManager = getActivity().getPackageManager();
329         try {
330             mInfo = packageManager.getApplicationInfo(mPackageName, 0);
331         } catch (PackageManager.NameNotFoundException e) {
332             LOG.e("Could not find package", e);
333         }
334         if (mInfo == null) {
335             return;
336         }
337         LoaderManager loaderManager = LoaderManager.getInstance(this);
338         mAppsStorageStatsManager.startLoading(loaderManager, mInfo, mUserId, mCacheCleared,
339                 mDataCleared);
340     }
341 
showClearDataDialog()342     private void showClearDataDialog() {
343         ConfirmationDialogFragment confirmClearStorageDialog =
344                 new ConfirmationDialogFragment.Builder(getContext())
345                         .setTitle(R.string.storage_clear_user_data_text)
346                         .setMessage(getString(R.string.storage_clear_data_dlg_text))
347                         .setPositiveButton(R.string.okay, mConfirmClearStorageDialog)
348                         .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
349                         .build();
350         showDialog(confirmClearStorageDialog, CONFIRM_CLEAR_STORAGE_DIALOG_TAG);
351     }
352 
showCannotClearDataDialog()353     private void showCannotClearDataDialog() {
354         ConfirmationDialogFragment dialogFragment =
355                 new ConfirmationDialogFragment.Builder(getContext())
356                         .setTitle(R.string.storage_clear_data_dlg_title)
357                         .setMessage(getString(R.string.storage_clear_failed_dlg_text))
358                         .setPositiveButton(R.string.okay, mConfirmCannotClearStorageDialog)
359                         .build();
360         showDialog(dialogFragment, CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG);
361     }
362 
363     private final Handler mHandler = new Handler() {
364         public void handleMessage(Message msg) {
365             if (getView() == null) {
366                 return;
367             }
368             switch (msg.what) {
369                 case MSG_CLEAR_USER_DATA:
370                     mDataCleared = true;
371                     mCacheCleared = true;
372                     processClearMsg(msg);
373                     break;
374                 case MSG_CLEAR_CACHE:
375                     mCacheCleared = true;
376                     // Refresh size info
377                     updateSize();
378                     break;
379             }
380         }
381     };
382 
383     class ClearCacheObserver extends IPackageDataObserver.Stub {
onRemoveCompleted(final String packageName, final boolean succeeded)384         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
385             Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE);
386             msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
387             mHandler.sendMessage(msg);
388         }
389     }
390 
391     class ClearUserDataObserver extends IPackageDataObserver.Stub {
onRemoveCompleted(final String packageName, final boolean succeeded)392         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
393             Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA);
394             msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
395             mHandler.sendMessage(msg);
396         }
397     }
398 }
399