1 /*
2  * Copyright (C) 2021 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.tv.settings.library.device.apps;
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.tv.settings.library.ManagerUtil.STATE_APP_MANAGEMENT;
23 import static com.android.tv.settings.library.device.apps.EnableDisablePreferenceController.KEY_ENABLE_DISABLE;
24 
25 import android.app.Activity;
26 import android.app.ActivityManager;
27 import android.content.ActivityNotFoundException;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.IPackageDataObserver;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.hardware.usb.IUsbManager;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.os.UserHandle;
40 import android.util.Log;
41 import android.widget.Toast;
42 
43 import com.android.tv.settings.library.ManagerUtil;
44 import com.android.tv.settings.library.PreferenceCompat;
45 import com.android.tv.settings.library.UIUpdateCallback;
46 import com.android.tv.settings.library.data.PreferenceControllerState;
47 import com.android.tv.settings.library.util.AbstractPreferenceController;
48 import com.android.tv.settings.library.util.ResourcesUtil;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 
53 /** State to handle app management settings screen. */
54 public class AppManagementState extends PreferenceControllerState {
55     private static final String TAG = "AppManagementState";
56     // Intent action implemented by apps that have open source licenses to display under settings
57     private static final String VIEW_LICENSES_ACTION = "com.android.tv.settings.VIEW_LICENSES";
58     private static final String ARG_PACKAGE_NAME = "packageName";
59 
60     private static final String KEY_VERSION = "version";
61     private static final String KEY_OPEN = "open";
62     private static final String KEY_LICENSES = "licenses";
63     private static final String KEY_PERMISSIONS = "permissions";
64 
65     // Result code identifiers
66     static final int REQUEST_UNINSTALL = 1;
67     static final int REQUEST_MANAGE_SPACE = 2;
68     static final int REQUEST_UNINSTALL_UPDATES = 3;
69     static final int REQUEST_CLEAR_DATA = 4;
70     static final int REQUEST_CLEAR_CACHE = 5;
71     static final int REQUEST_CLEAR_DEFAULTS = 6;
72 
73     private PackageManager mPackageManager;
74     private String mPackageName;
75     private ApplicationsState mApplicationsState;
76     private ApplicationsState.Session mSession;
77     private ApplicationsState.AppEntry mEntry;
78     private final ApplicationsState.Callbacks mCallbacks = new ApplicationsStateCallbacks();
79 
80     private ForceStopPreferenceController mForceStopPreferenceController;
81     private UninstallPreferenceController mUninstallPreferenceController;
82     private EnableDisablePreferenceController mEnableDisablePreferenceController;
83     private AppStoragePreferenceController mAppStoragePreferenceController;
84     private ClearDataPreferenceController mClearDataPreferenceController;
85     private ClearCachePreferenceController mClearCachePreferenceController;
86     private ClearDefaultsPreferenceController mClearDefaultsPreferenceController;
87     private NotificationsPreferenceController mNotificationsPreferenceController;
88     private final Handler mHandler = new Handler();
89 
AppManagementState(Context context, UIUpdateCallback callback)90     public AppManagementState(Context context,
91             UIUpdateCallback callback) {
92         super(context, callback);
93     }
94 
prepareArgs(Bundle args, String packageName)95     public static void prepareArgs(Bundle args, String packageName) {
96         args.putString(ARG_PACKAGE_NAME, packageName);
97     }
98 
99     @Override
onCreate(Bundle extras)100     public void onCreate(Bundle extras) {
101         mPackageName = extras.getString(ARG_PACKAGE_NAME);
102 
103         Activity activity = (Activity) mContext;
104         mPackageManager = activity.getPackageManager();
105         mApplicationsState = ApplicationsState.getInstance(activity.getApplication());
106         mSession = mApplicationsState.newSession(mCallbacks, getLifecycle());
107         mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId());
108         super.onCreate(extras);
109     }
110 
111     @Override
onResume()112     public void onResume() {
113         super.onResume();
114 
115         if (mEntry == null) {
116             Log.w(TAG, "App not found, trying to bail out");
117             mUIUpdateCallback.notifyNavigateBackward(getStateIdentifier());
118         }
119 
120         if (mClearDefaultsPreferenceController != null) {
121             mClearDefaultsPreferenceController.updateAndNotify();
122         }
123         if (mEnableDisablePreferenceController != null) {
124             mEnableDisablePreferenceController.updateAndNotify();
125         }
126         updatePrefs();
127     }
128 
129 
130     @Override
getStateIdentifier()131     public int getStateIdentifier() {
132         return STATE_APP_MANAGEMENT;
133     }
134 
135     @Override
onCreatePreferenceControllers(Context context)136     protected List<AbstractPreferenceController> onCreatePreferenceControllers(Context context) {
137         mForceStopPreferenceController = new ForceStopPreferenceController(
138                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
139                 mPreferenceCompatManager);
140         mUninstallPreferenceController = new UninstallPreferenceController(
141                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
142                 mPreferenceCompatManager);
143         mEnableDisablePreferenceController = new EnableDisablePreferenceController(
144                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
145                 mPreferenceCompatManager);
146         mAppStoragePreferenceController = new AppStoragePreferenceController(
147                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
148                 mPreferenceCompatManager);
149         mClearDataPreferenceController = new ClearDataPreferenceController(
150                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
151                 mPreferenceCompatManager);
152         mClearCachePreferenceController = new ClearCachePreferenceController(
153                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
154                 mPreferenceCompatManager);
155         mClearDefaultsPreferenceController = new ClearDefaultsPreferenceController(
156                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
157                 mPreferenceCompatManager);
158         mNotificationsPreferenceController = new NotificationsPreferenceController(
159                 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry,
160                 mPreferenceCompatManager);
161         List<AbstractPreferenceController> list = new ArrayList<>();
162         list.add(mForceStopPreferenceController);
163         list.add(mUninstallPreferenceController);
164         list.add(mEnableDisablePreferenceController);
165         list.add(mAppStoragePreferenceController);
166         list.add(mClearCachePreferenceController);
167         list.add(mClearDataPreferenceController);
168         list.add(mClearDefaultsPreferenceController);
169         list.add(mNotificationsPreferenceController);
170         return list;
171     }
172 
173     @Override
onPreferenceTreeClick(String[] key, boolean status)174     public boolean onPreferenceTreeClick(String[] key, boolean status) {
175         if (KEY_ENABLE_DISABLE.equals(key[0])) {
176             // disable the preference to prevent double clicking
177             mEnableDisablePreferenceController.setEnabled(false);
178         }
179         try {
180             return super.onPreferenceTreeClick(key, status);
181         } catch (ActivityNotFoundException e) {
182             Log.e(TAG, "Could not find activity to launch", e);
183             Toast.makeText(mContext,
184                     ResourcesUtil.getString(
185                             mContext, "device_apps_app_management_not_available"),
186                     Toast.LENGTH_SHORT).show();
187         }
188         return false;
189     }
190 
191     @Override
onActivityResult(int requestCode, int resultCode, Intent data)192     public void onActivityResult(int requestCode, int resultCode, Intent data) {
193         if (mEntry == null) {
194             return;
195         }
196         switch (requestCode) {
197             case REQUEST_UNINSTALL:
198                 final int deleteResult = data != null
199                         ? data.getIntExtra(Intent.EXTRA_INSTALL_RESULT, 0) : 0;
200                 if (deleteResult == PackageManager.DELETE_SUCCEEDED) {
201                     final int userId = UserHandle.getUserId(mEntry.info.uid);
202                     mApplicationsState.removePackage(mPackageName, userId);
203                     mUIUpdateCallback.notifyNavigateBackward(getStateIdentifier());
204                 } else {
205                     Log.e(TAG, "Uninstall failed with result " + deleteResult);
206                 }
207                 break;
208             case REQUEST_MANAGE_SPACE:
209                 mClearDataPreferenceController.setClearingData(false);
210                 if (resultCode == Activity.RESULT_OK) {
211                     final int userId = UserHandle.getUserId(mEntry.info.uid);
212                     mApplicationsState.requestSize(mPackageName, userId);
213                 } else {
214                     Log.w(TAG, "Failed to clear data!");
215                 }
216                 break;
217             case REQUEST_UNINSTALL_UPDATES:
218                 mUninstallPreferenceController.updateAndNotify();
219                 break;
220             case REQUEST_CLEAR_DATA:
221                 if (resultCode == Activity.RESULT_OK) {
222                     clearData();
223                 }
224                 break;
225             case REQUEST_CLEAR_CACHE:
226                 if (resultCode == Activity.RESULT_OK) {
227                     clearCache();
228                 }
229                 break;
230             case REQUEST_CLEAR_DEFAULTS:
231                 if (resultCode == Activity.RESULT_OK) {
232                     clearDefaults();
233                 }
234                 break;
235             default:
236                 break;
237         }
238     }
239 
clearDefaults()240     private void clearDefaults() {
241         PackageManager packageManager = mContext.getPackageManager();
242         packageManager.clearPackagePreferredActivities(mPackageName);
243         try {
244             final IBinder usbBinder = ServiceManager.getService(Context.USB_SERVICE);
245             IUsbManager.Stub.asInterface(usbBinder)
246                     .clearDefaults(mPackageName, UserHandle.myUserId());
247         } catch (RemoteException e) {
248             // Ignore
249         }
250     }
251 
clearData()252     private void clearData() {
253         if (!clearDataAllowed()) {
254             Log.e(TAG, "Attempt to clear data failed. Clear data is disabled for " + mPackageName);
255             return;
256         }
257 
258         mClearDataPreferenceController.setClearingData(true);
259         String spaceManagementActivityName = mEntry.info.manageSpaceActivityName;
260         if (spaceManagementActivityName != null) {
261             if (!ActivityManager.isUserAMonkey()) {
262                 Intent intent = new Intent(Intent.ACTION_DEFAULT);
263                 intent.setClassName(mEntry.info.packageName, spaceManagementActivityName);
264                 ((Activity) mContext).startActivityForResult(intent,
265                         ManagerUtil.calculateCompoundCode(getStateIdentifier(),
266                                 REQUEST_MANAGE_SPACE));
267             }
268         } else {
269             // Disabling clear cache preference while clearing data is in progress. See b/77815256
270             // for details.
271             mClearCachePreferenceController.setClearingCache(true);
272             ActivityManager am = mContext.getSystemService(ActivityManager.class);
273             boolean success = am.clearApplicationUserData(
274                     mEntry.info.packageName, new IPackageDataObserver.Stub() {
275                         public void onRemoveCompleted(
276                                 final String packageName, final boolean succeeded) {
277                             mHandler.post(new Runnable() {
278                                 @Override
279                                 public void run() {
280                                     mClearDataPreferenceController.setClearingData(false);
281                                     mClearCachePreferenceController.setClearingCache(false);
282                                     dataCleared(succeeded);
283                                 }
284                             });
285                         }
286                     });
287             if (!success) {
288                 mClearDataPreferenceController.setClearingData(false);
289                 dataCleared(false);
290             }
291         }
292         mClearDataPreferenceController.updateAndNotify();
293     }
294 
dataCleared(boolean succeeded)295     private void dataCleared(boolean succeeded) {
296         if (succeeded) {
297             final int userId = UserHandle.getUserId(mEntry.info.uid);
298             mApplicationsState.requestSize(mPackageName, userId);
299         } else {
300             Log.w(TAG, "Failed to clear data!");
301             mClearDataPreferenceController.update();
302         }
303     }
304 
305 
clearCache()306     private void clearCache() {
307         mClearCachePreferenceController.setClearingCache(true);
308         mPackageManager.deleteApplicationCacheFiles(mEntry.info.packageName,
309                 new IPackageDataObserver.Stub() {
310                     public void onRemoveCompleted(final String packageName,
311                             final boolean succeeded) {
312                         mHandler.post(new Runnable() {
313                             @Override
314                             public void run() {
315                                 mClearCachePreferenceController.setClearingCache(false);
316                                 cacheCleared(succeeded);
317                             }
318                         });
319                     }
320                 });
321         mClearCachePreferenceController.update();
322     }
323 
cacheCleared(boolean succeeded)324     private void cacheCleared(boolean succeeded) {
325         if (succeeded) {
326             final int userId = UserHandle.getUserId(mEntry.info.uid);
327             mApplicationsState.requestSize(mPackageName, userId);
328         } else {
329             Log.w(TAG, "Failed to clear cache!");
330             mClearCachePreferenceController.update();
331         }
332     }
333 
updatePrefs()334     private void updatePrefs() {
335         // Version
336         PreferenceCompat versionPreference = mPreferenceCompatManager
337                 .getOrCreatePrefCompat(KEY_VERSION);
338         if (versionPreference == null) {
339             versionPreference.setSelectable(false);
340         }
341         versionPreference.setTitle(
342                 ResourcesUtil.getString(mContext, "device_apps_app_management_version",
343                         mEntry.getVersion(mContext)));
344         versionPreference.setSummary(mPackageName);
345         mUIUpdateCallback.notifyUpdate(getStateIdentifier(), versionPreference);
346 
347         // Open
348         PreferenceCompat openPreference = mPreferenceCompatManager.getOrCreatePrefCompat(KEY_OPEN);
349         Intent appLaunchIntent =
350                 mPackageManager.getLeanbackLaunchIntentForPackage(mEntry.info.packageName);
351         if (appLaunchIntent == null) {
352             appLaunchIntent = mPackageManager.getLaunchIntentForPackage(mEntry.info.packageName);
353         }
354         if (appLaunchIntent != null) {
355             openPreference.setIntent(appLaunchIntent);
356             openPreference.setTitle(
357                     ResourcesUtil.getString(mContext, "device_apps_app_management_open"));
358             openPreference.setVisible(true);
359         } else {
360             openPreference.setVisible(false);
361         }
362         mUIUpdateCallback.notifyUpdate(getStateIdentifier(), openPreference);
363 
364         // Force stop
365         if (mForceStopPreferenceController != null) {
366             mForceStopPreferenceController.setEntry(mEntry);
367         }
368 
369         // Uninstall
370         if (mUninstallPreferenceController != null) {
371             mUninstallPreferenceController.setEntry(mEntry);
372         }
373 
374         // Disable/Enable
375         if (mEnableDisablePreferenceController != null) {
376             mEnableDisablePreferenceController.setEntry(mEntry);
377             mEnableDisablePreferenceController.setEnabled(true);
378         }
379 
380         // Storage used
381         if (mAppStoragePreferenceController != null) {
382             mAppStoragePreferenceController.setEntry(mEntry);
383         }
384 
385         // Clear data
386         if (clearDataAllowed() && mClearDataPreferenceController != null) {
387             mClearDataPreferenceController.setEntry(mEntry);
388         }
389 
390         // Clear cache
391         if (mClearCachePreferenceController != null) {
392             mClearCachePreferenceController.setEntry(mEntry);
393         }
394 
395         // Clear defaults
396         if (mClearDefaultsPreferenceController != null) {
397             mClearDefaultsPreferenceController.setEntry(mEntry);
398         }
399 
400         // Notifications
401         if (mNotificationsPreferenceController == null) {
402             mNotificationsPreferenceController.setEntry(mEntry);
403         }
404 
405         // Open Source Licenses
406         PreferenceCompat licensesPreference = mPreferenceCompatManager.getOrCreatePrefCompat(
407                 KEY_LICENSES);
408         // Check if app has open source licenses to display
409         Intent licenseIntent = new Intent(VIEW_LICENSES_ACTION);
410         licenseIntent.setPackage(mEntry.info.packageName);
411         ResolveInfo resolveInfo = resolveIntent(licenseIntent);
412         if (resolveInfo == null) {
413             licensesPreference.setVisible(false);
414         } else {
415             Intent intent = new Intent(licenseIntent);
416             intent.setClassName(resolveInfo.activityInfo.packageName,
417                     resolveInfo.activityInfo.name);
418             licensesPreference.setIntent(intent);
419             licensesPreference.setTitle(ResourcesUtil.getString(mContext,
420                     "device_apps_app_management_licenses"));
421             licensesPreference.setVisible(true);
422         }
423         mUIUpdateCallback.notifyUpdate(getStateIdentifier(), licensesPreference);
424 
425         // Permissions
426         PreferenceCompat permissionsPreference = mPreferenceCompatManager.getOrCreatePrefCompat(
427                 KEY_PERMISSIONS);
428         permissionsPreference.setTitle(ResourcesUtil.getString(mContext,
429                 "device_apps_app_management_permissions"));
430         permissionsPreference.setIntent(new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
431                 .putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName));
432         mUIUpdateCallback.notifyUpdate(getStateIdentifier(), permissionsPreference);
433     }
434 
435     private class ApplicationsStateCallbacks implements ApplicationsState.Callbacks {
436 
437         @Override
onRunningStateChanged(boolean running)438         public void onRunningStateChanged(boolean running) {
439             if (mForceStopPreferenceController != null) {
440                 mForceStopPreferenceController.update();
441             }
442         }
443 
444         @Override
onPackageListChanged()445         public void onPackageListChanged() {
446             if (mEntry == null || mEntry.info == null) {
447                 return;
448             }
449             final int userId = UserHandle.getUserId(mEntry.info.uid);
450             mEntry = mApplicationsState.getEntry(mPackageName, userId);
451             if (mEntry == null) {
452                 mUIUpdateCallback.notifyNavigateBackward(getStateIdentifier());
453             }
454             updatePrefs();
455         }
456 
457         @Override
onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)458         public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
459         }
460 
461         @Override
onPackageIconChanged()462         public void onPackageIconChanged() {
463         }
464 
465         @Override
onPackageSizeChanged(String packageName)466         public void onPackageSizeChanged(String packageName) {
467             if (mAppStoragePreferenceController == null) {
468                 // Nothing to do here.
469                 return;
470             }
471             mAppStoragePreferenceController.updateAndNotify();
472             if (mClearCachePreferenceController != null) {
473                 mClearCachePreferenceController.updateAndNotify();
474             }
475 
476             if (mClearDataPreferenceController != null) {
477                 mClearDataPreferenceController.updateAndNotify();
478             }
479         }
480 
481         @Override
onAllSizesComputed()482         public void onAllSizesComputed() {
483             if (mAppStoragePreferenceController == null) {
484                 // Nothing to do here.
485                 return;
486             }
487             mAppStoragePreferenceController.updateAndNotify();
488             if (mClearCachePreferenceController != null) {
489                 mClearCachePreferenceController.updateAndNotify();
490             }
491 
492             if (mClearDataPreferenceController != null) {
493                 mClearDataPreferenceController.updateAndNotify();
494             }
495         }
496 
497         @Override
onLauncherInfoChanged()498         public void onLauncherInfoChanged() {
499             updatePrefs();
500         }
501 
502         @Override
onLoadEntriesCompleted()503         public void onLoadEntriesCompleted() {
504             mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId());
505             updatePrefs();
506             if (mAppStoragePreferenceController == null) {
507                 // Nothing to do here.
508                 return;
509             }
510             mAppStoragePreferenceController.updateAndNotify();
511             if (mClearCachePreferenceController != null) {
512                 mClearCachePreferenceController.updateAndNotify();
513             }
514 
515             if (mClearDataPreferenceController != null) {
516                 mClearDataPreferenceController.updateAndNotify();
517             }
518         }
519     }
520 
resolveIntent(Intent intent)521     private ResolveInfo resolveIntent(Intent intent) {
522         List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, 0);
523         return (resolveInfos == null || resolveInfos.size() <= 0) ? null : resolveInfos.get(0);
524     }
525 
526     /**
527      * Clearing data can only be disabled for system apps. For all non-system apps it is enabled.
528      * System apps disable it explicitly via the android:allowClearUserData tag.
529      **/
clearDataAllowed()530     private boolean clearDataAllowed() {
531         boolean sysApp = (mEntry.info.flags & FLAG_SYSTEM) == FLAG_SYSTEM;
532         boolean allowClearData =
533                 (mEntry.info.flags & FLAG_ALLOW_CLEAR_USER_DATA) == FLAG_ALLOW_CLEAR_USER_DATA;
534         return !sysApp || allowClearData;
535     }
536 }
537