• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA;
20 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
21 
22 import static com.android.car.settings.common.ActionButtonsPreference.ActionButtons;
23 
24 import android.app.ActivityManager;
25 import android.car.drivingstate.CarUxRestrictions;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.IPackageDataObserver;
30 import android.content.pm.PackageManager;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 
36 import androidx.annotation.VisibleForTesting;
37 import androidx.loader.app.LoaderManager;
38 
39 import com.android.car.settings.R;
40 import com.android.car.settings.common.ActionButtonInfo;
41 import com.android.car.settings.common.ActionButtonsPreference;
42 import com.android.car.settings.common.ConfirmationDialogFragment;
43 import com.android.car.settings.common.FragmentController;
44 import com.android.car.settings.common.Logger;
45 import com.android.car.settings.common.PreferenceController;
46 import com.android.settingslib.RestrictedLockUtils;
47 import com.android.settingslib.RestrictedLockUtilsInternal;
48 import com.android.settingslib.applications.ApplicationsState;
49 import com.android.settingslib.applications.StorageStatsSource;
50 
51 /**
52  * Displays the action buttons to clear an applications cache and user data.
53  */
54 public class StorageApplicationActionButtonsPreferenceController extends
55         PreferenceController<ActionButtonsPreference> implements
56         AppsStorageStatsManager.Callback {
57     private static final Logger LOG = new Logger(
58             StorageApplicationActionButtonsPreferenceController.class);
59 
60     @VisibleForTesting
61     static final String CONFIRM_CLEAR_STORAGE_DIALOG_TAG =
62             "com.android.car.settings.storage.ConfirmClearStorageDialog";
63 
64     @VisibleForTesting
65     static final String CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG =
66             "com.android.car.settings.storage.ConfirmCannotClearStorageDialog";
67 
68     public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
69     // Result code identifiers
70     public static final int REQUEST_MANAGE_SPACE = 2;
71 
72     // Internal constants used in Handler
73     private static final int OP_SUCCESSFUL = 1;
74     private static final int OP_FAILED = 2;
75 
76     // Constant used in handler to determine when the user data is cleared.
77     private static final int MSG_CLEAR_USER_DATA = 1;
78     // Constant used in handler to determine when the cache is cleared.
79     private static final int MSG_CLEAR_CACHE = 2;
80 
81     private ActionButtonInfo mClearStorageButton;
82     private ActionButtonInfo mClearCacheButton;
83 
84     private ApplicationsState.AppEntry mAppEntry;
85     private String mPackageName;
86     private ApplicationInfo mInfo;
87     private AppsStorageStatsManager mAppsStorageStatsManager;
88     private LoaderManager mLoaderManager;
89 
90     //  An observer callback to get notified when the cache file deletion is complete.
91     private ClearCacheObserver mClearCacheObserver;
92     //  An observer callback to get notified when the user data deletion is complete.
93     private ClearUserDataObserver mClearDataObserver;
94 
95     private PackageManager mPackageManager;
96     private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin;
97     private boolean mAppsControlDisallowedBySystem;
98     private int mUserId;
99 
100     private boolean mCacheCleared;
101     private boolean mDataCleared;
102 
103     private final ConfirmationDialogFragment.ConfirmListener mConfirmClearStorageDialog =
104             arguments -> initiateClearUserData();
105 
106     private final ConfirmationDialogFragment.ConfirmListener mConfirmCannotClearStorageDialog =
107             arguments -> mClearStorageButton.setEnabled(false);
108 
StorageApplicationActionButtonsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)109     public StorageApplicationActionButtonsPreferenceController(Context context,
110             String preferenceKey, FragmentController fragmentController,
111             CarUxRestrictions uxRestrictions) {
112         super(context, preferenceKey, fragmentController, uxRestrictions);
113         mUserId = UserHandle.myUserId();
114         mPackageManager = context.getPackageManager();
115     }
116 
117     @Override
getPreferenceType()118     protected Class<ActionButtonsPreference> getPreferenceType() {
119         return ActionButtonsPreference.class;
120     }
121 
122     /**
123      * Sets the {@link ApplicationsState.AppEntry} which is used to load the app name and icon.
124      */
setAppEntry( ApplicationsState.AppEntry appEntry)125     public StorageApplicationActionButtonsPreferenceController setAppEntry(
126             ApplicationsState.AppEntry appEntry) {
127         mAppEntry = appEntry;
128         return this;
129     }
130 
131     /**
132      * Set the packageName, which is used to perform actions on a particular package.
133      */
setPackageName(String packageName)134     public StorageApplicationActionButtonsPreferenceController setPackageName(String packageName) {
135         mPackageName = packageName;
136         return this;
137     }
138 
139     /**
140      * Sets the {@link AppsStorageStatsManager} which will be used to register the controller to the
141      * Listener {@link AppsStorageStatsManager.Callback}.
142      */
setAppsStorageStatsManager( AppsStorageStatsManager appsStorageStatsManager)143     public StorageApplicationActionButtonsPreferenceController setAppsStorageStatsManager(
144             AppsStorageStatsManager appsStorageStatsManager) {
145         mAppsStorageStatsManager = appsStorageStatsManager;
146         return this;
147     }
148 
149     /**
150      * Sets the {@link LoaderManager} used to load app storage stats.
151      */
setLoaderManager( LoaderManager loaderManager)152     public StorageApplicationActionButtonsPreferenceController setLoaderManager(
153             LoaderManager loaderManager) {
154         mLoaderManager = loaderManager;
155         return this;
156     }
157 
158     @Override
checkInitialized()159     protected void checkInitialized() {
160         if (mAppEntry == null || mPackageName == null || mAppsStorageStatsManager == null
161                 || mLoaderManager == null) {
162             throw new IllegalStateException(
163                     "AppEntry, PackageName, AppStorageStatsManager, and LoaderManager should be "
164                             + "set before calling this function");
165         }
166     }
167 
168     @Override
onCreateInternal()169     protected void onCreateInternal() {
170         mAppsStorageStatsManager.registerListener(this);
171 
172         mClearStorageButton = getPreference().getButton(ActionButtons.BUTTON1);
173         mClearCacheButton = getPreference().getButton(ActionButtons.BUTTON2);
174 
175         ConfirmationDialogFragment.resetListeners(
176                 (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
177                         CONFIRM_CLEAR_STORAGE_DIALOG_TAG),
178                 mConfirmClearStorageDialog,
179                 /* rejectListener= */ null,
180                 /* neutralListener= */ null);
181         ConfirmationDialogFragment.resetListeners(
182                 (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
183                         CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG),
184                 mConfirmCannotClearStorageDialog,
185                 /* rejectListener= */ null,
186                 /* neutralListener= */ null);
187 
188         mClearStorageButton
189                 .setText(R.string.storage_clear_user_data_text)
190                 .setIcon(R.drawable.ic_delete)
191                 .setOnClickListener(i -> handleClearDataClick())
192                 .setEnabled(false);
193         mClearCacheButton
194                 .setText(R.string.storage_clear_cache_btn_text)
195                 .setIcon(R.drawable.ic_delete)
196                 .setOnClickListener(i -> handleClearCacheClick())
197                 .setEnabled(false);
198     }
199 
200     @Override
onStartInternal()201     protected void onStartInternal() {
202         mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
203                 getContext(), UserManager.DISALLOW_APPS_CONTROL, mUserId);
204         mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction(
205                 getContext(), UserManager.DISALLOW_APPS_CONTROL, mUserId);
206     }
207 
208     @Override
updateState(ActionButtonsPreference preference)209     protected void updateState(ActionButtonsPreference preference) {
210         try {
211             mInfo = mPackageManager.getApplicationInfo(mPackageName, 0);
212         } catch (PackageManager.NameNotFoundException e) {
213             LOG.e("Could not find package", e);
214         }
215         if (mInfo == null) {
216             return;
217         }
218         mAppsStorageStatsManager.startLoading(mLoaderManager, mInfo, mUserId, mCacheCleared,
219                 mDataCleared);
220     }
221 
222     @Override
onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared, boolean dataCleared)223     public void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared,
224             boolean dataCleared) {
225         if (data == null || mAppsControlDisallowedBySystem) {
226             mClearStorageButton.setEnabled(false);
227             mClearCacheButton.setEnabled(false);
228         } else {
229             long cacheSize = data.getCacheBytes();
230             long dataSize = data.getDataBytes() - cacheSize;
231 
232             mClearStorageButton.setEnabled(
233                     dataSize > 0 && !mDataCleared && clearDataAllowedBySystemFlag());
234             mClearCacheButton.setEnabled(cacheSize > 0 && !mCacheCleared);
235         }
236     }
237 
238     @VisibleForTesting
setPackageManager(PackageManager packageManager)239     void setPackageManager(PackageManager packageManager) {
240         mPackageManager = packageManager;
241     }
242 
243     @VisibleForTesting
setAppsControlDisallowedAdmin(RestrictedLockUtils.EnforcedAdmin admin)244     void setAppsControlDisallowedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
245         mAppsControlDisallowedAdmin = admin;
246     }
247 
248     @VisibleForTesting
setAppsControlDisallowedBySystem(boolean disallowed)249     void setAppsControlDisallowedBySystem(boolean disallowed) {
250         mAppsControlDisallowedBySystem = disallowed;
251     }
252 
handleClearCacheClick()253     private void handleClearCacheClick() {
254         if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
255             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
256                     getContext(), mAppsControlDisallowedAdmin);
257             return;
258         }
259         // Lazy initialization of observer.
260         if (mClearCacheObserver == null) {
261             mClearCacheObserver = new ClearCacheObserver();
262         }
263         mPackageManager.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver);
264     }
265 
handleClearDataClick()266     private void handleClearDataClick() {
267         if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
268             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
269                     getContext(), mAppsControlDisallowedAdmin);
270         } else {
271             Intent intent = new Intent(Intent.ACTION_DEFAULT);
272             boolean isManageSpaceActivityAvailable = false;
273             if (mAppEntry.info.manageSpaceActivityName != null) {
274                 intent.setClassName(mAppEntry.info.packageName,
275                         mAppEntry.info.manageSpaceActivityName);
276                 isManageSpaceActivityAvailable = mPackageManager.resolveActivity(
277                         intent, /* flags= */ 0) != null;
278             }
279 
280             if (isManageSpaceActivityAvailable) {
281                 getFragmentController().startActivityForResult(intent,
282                         REQUEST_MANAGE_SPACE, /* callback= */ null);
283             } else {
284                 showClearDataDialog();
285             }
286         }
287     }
288 
289     /**
290      * Clearing data can only be disabled for system apps. For all non-system apps it is enabled.
291      * System apps disable it explicitly via the android:allowClearUserData tag.
292      */
clearDataAllowedBySystemFlag()293     private boolean clearDataAllowedBySystemFlag() {
294         boolean sysApp = (mAppEntry.info.flags & FLAG_SYSTEM) == FLAG_SYSTEM;
295         boolean allowClearData =
296                 (mAppEntry.info.flags & FLAG_ALLOW_CLEAR_USER_DATA) == FLAG_ALLOW_CLEAR_USER_DATA;
297         return !sysApp || allowClearData;
298     }
299 
300 
301     /*
302      * Private method to initiate clearing user data when the user clicks the clear data
303      * button for a system package
304      */
initiateClearUserData()305     private void initiateClearUserData() {
306         mClearStorageButton.setEnabled(false);
307         // Invoke uninstall or clear user data based on sysPackage
308         String packageName = mAppEntry.info.packageName;
309         LOG.i("Clearing user data for package : " + packageName);
310         if (mClearDataObserver == null) {
311             mClearDataObserver = new ClearUserDataObserver();
312         }
313         ActivityManager am = (ActivityManager)
314                 getContext().getSystemService(Context.ACTIVITY_SERVICE);
315         boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
316         if (!res) {
317             // Clearing data failed for some obscure reason. Just log error for now
318             LOG.i("Couldn't clear application user data for package:" + packageName);
319             showCannotClearDataDialog();
320         }
321     }
322 
323     /*
324      * Private method to handle clear message notification from observer when
325      * the async operation from PackageManager is complete
326      */
processClearMsg(Message msg)327     private void processClearMsg(Message msg) {
328         int result = msg.arg1;
329         String packageName = mAppEntry.info.packageName;
330         if (result == OP_SUCCESSFUL) {
331             LOG.i("Cleared user data for package : " + packageName);
332             refreshUi();
333         } else {
334             mClearStorageButton.setEnabled(true);
335         }
336     }
337 
showClearDataDialog()338     private void showClearDataDialog() {
339         ConfirmationDialogFragment confirmClearStorageDialog =
340                 new ConfirmationDialogFragment.Builder(getContext())
341                         .setTitle(R.string.storage_clear_user_data_text)
342                         .setMessage(getContext().getString(R.string.storage_clear_data_dlg_text))
343                         .setPositiveButton(R.string.okay, mConfirmClearStorageDialog)
344                         .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
345                         .build();
346         getFragmentController().showDialog(confirmClearStorageDialog,
347                 CONFIRM_CLEAR_STORAGE_DIALOG_TAG);
348     }
349 
showCannotClearDataDialog()350     private void showCannotClearDataDialog() {
351         ConfirmationDialogFragment dialogFragment =
352                 new ConfirmationDialogFragment.Builder(getContext())
353                         .setTitle(R.string.storage_clear_data_dlg_title)
354                         .setMessage(getContext().getString(R.string.storage_clear_failed_dlg_text))
355                         .setPositiveButton(R.string.okay, mConfirmCannotClearStorageDialog)
356                         .build();
357         getFragmentController().showDialog(dialogFragment, CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG);
358     }
359 
360     private final Handler mHandler = new Handler() {
361         public void handleMessage(Message msg) {
362             switch (msg.what) {
363                 case MSG_CLEAR_USER_DATA:
364                     mDataCleared = true;
365                     mCacheCleared = true;
366                     processClearMsg(msg);
367                     break;
368                 case MSG_CLEAR_CACHE:
369                     mCacheCleared = true;
370                     // Refresh info
371                     refreshUi();
372                     break;
373             }
374         }
375     };
376 
377     class ClearCacheObserver extends IPackageDataObserver.Stub {
onRemoveCompleted(final String packageName, final boolean succeeded)378         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
379             Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE);
380             msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
381             mHandler.sendMessage(msg);
382         }
383     }
384 
385     class ClearUserDataObserver extends IPackageDataObserver.Stub {
onRemoveCompleted(final String packageName, final boolean succeeded)386         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
387             Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA);
388             msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
389             mHandler.sendMessage(msg);
390         }
391     }
392 }
393