• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tv.settings.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.util.InstrumentationUtils.logEntrySelected;
23 
24 import android.app.Activity;
25 import android.app.ActivityManager;
26 import android.app.tvsettings.TvSettingsEnums;
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.graphics.drawable.Drawable;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.UserHandle;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.widget.Toast;
40 
41 import androidx.annotation.NonNull;
42 import androidx.preference.Preference;
43 import androidx.preference.PreferenceScreen;
44 
45 import com.android.settingslib.applications.ApplicationsState;
46 import com.android.tv.settings.R;
47 import com.android.tv.settings.SettingsPreferenceFragment;
48 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 
53 /**
54  * Fragment for managing a single app
55  */
56 public class AppManagementFragment extends SettingsPreferenceFragment {
57     private static final String TAG = "AppManagementFragment";
58 
59     private static final String ARG_PACKAGE_NAME = "packageName";
60 
61     private static final String KEY_VERSION = "version";
62     private static final String KEY_OPEN = "open";
63     private static final String KEY_FORCE_STOP = "forceStop";
64     private static final String KEY_UNINSTALL = "uninstall";
65     private static final String KEY_ENABLE_DISABLE = "enableDisable";
66     private static final String KEY_APP_STORAGE = "appStorage";
67     private static final String KEY_CLEAR_DATA = "clearData";
68     private static final String KEY_CLEAR_CACHE = "clearCache";
69     private static final String KEY_CLEAR_DEFAULTS = "clearDefaults";
70     private static final String KEY_NOTIFICATIONS = "notifications";
71     private static final String KEY_PERMISSIONS = "permissions";
72     private static final String KEY_LICENSES = "licenses";
73 
74     // Intent action implemented by apps that have open source licenses to display under settings
75     private static final String VIEW_LICENSES_ACTION = "com.android.tv.settings.VIEW_LICENSES";
76 
77     // Result code identifiers
78     private static final int REQUEST_UNINSTALL = 1;
79     private static final int REQUEST_MANAGE_SPACE = 2;
80     private static final int REQUEST_UNINSTALL_UPDATES = 3;
81 
82     private PackageManager mPackageManager;
83     private String mPackageName;
84     private ApplicationsState mApplicationsState;
85     private ApplicationsState.Session mSession;
86     private ApplicationsState.AppEntry mEntry;
87     private final ApplicationsState.Callbacks mCallbacks = new ApplicationsStateCallbacks();
88 
89     private ForceStopPreference mForceStopPreference;
90     private UninstallPreference mUninstallPreference;
91     private EnableDisablePreference mEnableDisablePreference;
92     private AppStoragePreference mAppStoragePreference;
93     private ClearDataPreference mClearDataPreference;
94     private ClearCachePreference mClearCachePreference;
95     private ClearDefaultsPreference mClearDefaultsPreference;
96     private NotificationsPreference mNotificationsPreference;
97 
98     private final Handler mHandler = new Handler();
99     private Runnable mBailoutRunnable = () -> {
100         if (isResumed() && !getFragmentManager().popBackStackImmediate()) {
101             getActivity().onBackPressed();
102         }
103     };
104 
prepareArgs(@onNull Bundle args, String packageName)105     public static void prepareArgs(@NonNull Bundle args, String packageName) {
106         args.putString(ARG_PACKAGE_NAME, packageName);
107     }
108 
109     @Override
onCreate(Bundle savedInstanceState)110     public void onCreate(Bundle savedInstanceState) {
111         mPackageName = getArguments().getString(ARG_PACKAGE_NAME);
112 
113         final Activity activity = getActivity();
114         mPackageManager = activity.getPackageManager();
115         mApplicationsState = ApplicationsState.getInstance(activity.getApplication());
116         mSession = mApplicationsState.newSession(mCallbacks, getLifecycle());
117         mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId());
118 
119         super.onCreate(savedInstanceState);
120     }
121 
122     @Override
onResume()123     public void onResume() {
124         super.onResume();
125 
126         if (mEntry == null) {
127             Log.w(TAG, "App not found, trying to bail out");
128             navigateBack();
129         }
130 
131         if (mClearDefaultsPreference != null) {
132             mClearDefaultsPreference.refresh();
133         }
134         if (mEnableDisablePreference != null) {
135             mEnableDisablePreference.refresh();
136         }
137     }
138 
139     @Override
onPause()140     public void onPause() {
141         super.onPause();
142         mHandler.removeCallbacks(mBailoutRunnable);
143     }
144 
145     @Override
onActivityResult(int requestCode, int resultCode, Intent data)146     public void onActivityResult(int requestCode, int resultCode, Intent data) {
147         super.onActivityResult(requestCode, resultCode, data);
148         if (mEntry == null) {
149             return;
150         }
151         switch (requestCode) {
152             case REQUEST_UNINSTALL:
153                 final int deleteResult = data != null
154                         ? data.getIntExtra(Intent.EXTRA_INSTALL_RESULT, 0) : 0;
155                 if (deleteResult == PackageManager.DELETE_SUCCEEDED) {
156                     final int userId = UserHandle.getUserId(mEntry.info.uid);
157                     mApplicationsState.removePackage(mPackageName, userId);
158                     navigateBack();
159                 } else {
160                     Log.e(TAG, "Uninstall failed with result " + deleteResult);
161                 }
162                 break;
163             case REQUEST_MANAGE_SPACE:
164                 mClearDataPreference.setClearingData(false);
165                 if (resultCode == Activity.RESULT_OK) {
166                     final int userId = UserHandle.getUserId(mEntry.info.uid);
167                     mApplicationsState.requestSize(mPackageName, userId);
168                 } else {
169                     Log.w(TAG, "Failed to clear data!");
170                 }
171                 break;
172             case REQUEST_UNINSTALL_UPDATES:
173                 mUninstallPreference.refresh();
174                 break;
175         }
176     }
177 
navigateBack()178     private void navigateBack() {
179         if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
180             TwoPanelSettingsFragment parentFragment =
181                     (TwoPanelSettingsFragment) getCallbackFragment();
182             if (parentFragment.isFragmentInTheMainPanel(this)) {
183                 parentFragment.navigateBack();
184             }
185         } else {
186             // need to post this to avoid recursing in the fragment manager.
187             mHandler.removeCallbacks(mBailoutRunnable);
188             mHandler.post(mBailoutRunnable);
189         }
190     }
191 
192     @Override
onPreferenceTreeClick(Preference preference)193     public boolean onPreferenceTreeClick(Preference preference) {
194         if (preference.equals(mEnableDisablePreference)) {
195             // disable the preference to prevent double clicking
196             mHandler.post(() -> {
197                 mEnableDisablePreference.setEnabled(false);
198             });
199         }
200         final Intent intent = preference.getIntent();
201         if (intent != null) {
202             try {
203                 if (preference.equals(mUninstallPreference)) {
204                     startActivityForResult(intent, mUninstallPreference.canUninstall()
205                             ? REQUEST_UNINSTALL : REQUEST_UNINSTALL_UPDATES);
206                 } else {
207                     startActivity(intent);
208                 }
209             } catch (ActivityNotFoundException e) {
210                 Log.e(TAG, "Could not find activity to launch", e);
211                 Toast.makeText(getContext(), R.string.device_apps_app_management_not_available,
212                         Toast.LENGTH_SHORT).show();
213             }
214             return true;
215         } else {
216             return super.onPreferenceTreeClick(preference);
217         }
218     }
219 
220     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)221     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
222         final Context themedContext = getPreferenceManager().getContext();
223         final PreferenceScreen screen =
224                 getPreferenceManager().createPreferenceScreen(themedContext);
225         screen.setTitle(getAppName());
226         setPreferenceScreen(screen);
227 
228         updatePrefs();
229 
230         if (Intent.ACTION_AUTO_REVOKE_PERMISSIONS.equals(getActivity().getIntent().getAction())) {
231             scrollToPreference(findPreference(KEY_PERMISSIONS));
232         }
233     }
234 
updatePrefs()235     private void updatePrefs() {
236         if (mEntry == null) {
237             final PreferenceScreen screen = getPreferenceScreen();
238             screen.removeAll();
239             return;
240         }
241         final Context themedContext = getPreferenceManager().getContext();
242 
243         // Version
244         Preference versionPreference = findPreference(KEY_VERSION);
245         if (versionPreference == null) {
246             versionPreference = new Preference(themedContext);
247             versionPreference.setKey(KEY_VERSION);
248             replacePreference(versionPreference);
249             versionPreference.setSelectable(false);
250         }
251         versionPreference.setTitle(getString(R.string.device_apps_app_management_version,
252                 mEntry.getVersion(getActivity())));
253         versionPreference.setSummary(mPackageName);
254 
255         // Open
256         Preference openPreference = findPreference(KEY_OPEN);
257         if (openPreference == null) {
258             openPreference = new Preference(themedContext);
259             openPreference.setKey(KEY_OPEN);
260             replacePreference(openPreference);
261         }
262         Intent appLaunchIntent =
263                 mPackageManager.getLeanbackLaunchIntentForPackage(mEntry.info.packageName);
264         if (appLaunchIntent == null) {
265             appLaunchIntent = mPackageManager.getLaunchIntentForPackage(mEntry.info.packageName);
266         }
267         if (appLaunchIntent != null) {
268             openPreference.setIntent(appLaunchIntent);
269             openPreference.setTitle(R.string.device_apps_app_management_open);
270             openPreference.setVisible(true);
271             openPreference.setOnPreferenceClickListener(
272                     preference -> {
273                         logEntrySelected(TvSettingsEnums.APPS_ALL_APPS_APP_ENTRY_OPEN);
274                         return false;
275                     });
276         } else {
277             openPreference.setVisible(false);
278         }
279 
280         // Force stop
281         if (mForceStopPreference == null) {
282             mForceStopPreference = new ForceStopPreference(themedContext, mEntry);
283             mForceStopPreference.setKey(KEY_FORCE_STOP);
284             replacePreference(mForceStopPreference);
285         } else {
286             mForceStopPreference.setEntry(mEntry);
287         }
288 
289         // Uninstall
290         if (mUninstallPreference == null) {
291             mUninstallPreference = new UninstallPreference(themedContext, mEntry);
292             mUninstallPreference.setKey(KEY_UNINSTALL);
293             replacePreference(mUninstallPreference);
294         } else {
295             mUninstallPreference.setEntry(mEntry);
296         }
297 
298         // Disable/Enable
299         if (mEnableDisablePreference == null) {
300             mEnableDisablePreference = new EnableDisablePreference(themedContext, mEntry);
301             mEnableDisablePreference.setKey(KEY_ENABLE_DISABLE);
302             replacePreference(mEnableDisablePreference);
303         } else {
304             mEnableDisablePreference.setEntry(mEntry);
305             if (!mEnableDisablePreference.isRestricted()) {
306                 mEnableDisablePreference.setEnabled(true);
307             }
308         }
309 
310         // Storage used
311         if (mAppStoragePreference == null) {
312             mAppStoragePreference = new AppStoragePreference(themedContext, mEntry);
313             mAppStoragePreference.setKey(KEY_APP_STORAGE);
314             replacePreference(mAppStoragePreference);
315         } else {
316             mAppStoragePreference.setEntry(mEntry);
317         }
318 
319         // Clear data
320         if (clearDataAllowed()) {
321             if (mClearDataPreference == null) {
322                 mClearDataPreference = new ClearDataPreference(themedContext, mEntry);
323                 mClearDataPreference.setKey(KEY_CLEAR_DATA);
324                 replacePreference(mClearDataPreference);
325             } else {
326                 mClearDataPreference.setEntry(mEntry);
327             }
328         }
329 
330         // Clear cache
331         if (mClearCachePreference == null) {
332             mClearCachePreference = new ClearCachePreference(themedContext, mEntry);
333             mClearCachePreference.setKey(KEY_CLEAR_CACHE);
334             replacePreference(mClearCachePreference);
335         } else {
336             mClearCachePreference.setEntry(mEntry);
337         }
338 
339         // Clear defaults
340         if (mClearDefaultsPreference == null) {
341             mClearDefaultsPreference = new ClearDefaultsPreference(themedContext, mEntry);
342             mClearDefaultsPreference.setKey(KEY_CLEAR_DEFAULTS);
343             replacePreference(mClearDefaultsPreference);
344         } else {
345             mClearDefaultsPreference.setEntry(mEntry);
346         }
347 
348         // Notifications
349         if (mNotificationsPreference == null) {
350             mNotificationsPreference = new NotificationsPreference(themedContext, mEntry);
351             mNotificationsPreference.setKey(KEY_NOTIFICATIONS);
352             replacePreference(mNotificationsPreference);
353         } else {
354             mNotificationsPreference.setEntry(mEntry);
355         }
356 
357         // Open Source Licenses
358         Preference licensesPreference = findPreference(KEY_LICENSES);
359         if (licensesPreference == null) {
360             licensesPreference = new Preference(themedContext);
361             licensesPreference.setKey(KEY_LICENSES);
362             replacePreference(licensesPreference);
363         }
364         // Check if app has open source licenses to display
365         Intent licenseIntent = new Intent(VIEW_LICENSES_ACTION);
366         licenseIntent.setPackage(mEntry.info.packageName);
367         ResolveInfo resolveInfo = resolveIntent(licenseIntent);
368         if (resolveInfo == null) {
369             licensesPreference.setVisible(false);
370         } else {
371             Intent intent = new Intent(licenseIntent);
372             intent.setClassName(resolveInfo.activityInfo.packageName,
373                     resolveInfo.activityInfo.name);
374             licensesPreference.setIntent(intent);
375             licensesPreference.setTitle(R.string.device_apps_app_management_licenses);
376             licensesPreference.setOnPreferenceClickListener(
377                     preference -> {
378                         logEntrySelected(TvSettingsEnums.APPS_ALL_APPS_APP_ENTRY_LICENSES);
379                         return false;
380                     });
381             licensesPreference.setVisible(true);
382         }
383 
384         // Permissions
385         Preference permissionsPreference = findPreference(KEY_PERMISSIONS);
386         if (permissionsPreference == null) {
387             permissionsPreference = new Preference(themedContext);
388             permissionsPreference.setKey(KEY_PERMISSIONS);
389             permissionsPreference.setTitle(R.string.device_apps_app_management_permissions);
390             replacePreference(permissionsPreference);
391         }
392         permissionsPreference.setOnPreferenceClickListener(
393                 preference -> {
394                     logEntrySelected(TvSettingsEnums.APPS_ALL_APPS_APP_ENTRY_PERMISSIONS);
395                     return false;
396                 });
397         permissionsPreference.setIntent(new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
398                 .putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName));
399     }
400 
replacePreference(Preference preference)401     private void replacePreference(Preference preference) {
402         final String key = preference.getKey();
403         if (TextUtils.isEmpty(key)) {
404             throw new IllegalArgumentException("Can't replace a preference without a key");
405         }
406         final Preference old = findPreference(key);
407         if (old != null) {
408             getPreferenceScreen().removePreference(old);
409         }
410         getPreferenceScreen().addPreference(preference);
411     }
412 
resolveIntent(Intent intent)413     private ResolveInfo resolveIntent(Intent intent) {
414         List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, 0);
415         return (resolveInfos == null || resolveInfos.size() <= 0) ? null : resolveInfos.get(0);
416     }
417 
getAppName()418     public String getAppName() {
419         if (mEntry == null) {
420             return null;
421         }
422         mEntry.ensureLabel(getActivity());
423         return mEntry.label;
424     }
425 
getAppIcon()426     public Drawable getAppIcon() {
427         if (mEntry == null) {
428             return null;
429         }
430         mApplicationsState.ensureIcon(mEntry);
431         return mEntry.icon;
432     }
433 
clearData()434     public void clearData() {
435         if (!clearDataAllowed()) {
436             Log.e(TAG, "Attempt to clear data failed. Clear data is disabled for " + mPackageName);
437             return;
438         }
439 
440         mClearDataPreference.setClearingData(true);
441         String spaceManagementActivityName = mEntry.info.manageSpaceActivityName;
442         if (spaceManagementActivityName != null) {
443             if (!ActivityManager.isUserAMonkey()) {
444                 Intent intent = new Intent(Intent.ACTION_DEFAULT);
445                 intent.setClassName(mEntry.info.packageName, spaceManagementActivityName);
446                 startActivityForResult(intent, REQUEST_MANAGE_SPACE);
447             }
448         } else {
449             // Disabling clear cache preference while clearing data is in progress. See b/77815256
450             // for details.
451             mClearCachePreference.setClearingCache(true);
452             ActivityManager am = (ActivityManager) getActivity().getSystemService(
453                     Context.ACTIVITY_SERVICE);
454             boolean success = am.clearApplicationUserData(
455                     mEntry.info.packageName, new IPackageDataObserver.Stub() {
456                         public void onRemoveCompleted(
457                                 final String packageName, final boolean succeeded) {
458                             mHandler.post(new Runnable() {
459                                 @Override
460                                 public void run() {
461                                     mClearDataPreference.setClearingData(false);
462                                     mClearCachePreference.setClearingCache(false);
463                                     if (succeeded) {
464                                         dataCleared(true);
465                                     } else {
466                                         dataCleared(false);
467                                     }
468                                 }
469                             });
470                         }
471                     });
472             if (!success) {
473                 mClearDataPreference.setClearingData(false);
474                 dataCleared(false);
475             }
476         }
477         mClearDataPreference.refresh();
478     }
479 
dataCleared(boolean succeeded)480     private void dataCleared(boolean succeeded) {
481         if (succeeded) {
482             final int userId =  UserHandle.getUserId(mEntry.info.uid);
483             mApplicationsState.requestSize(mPackageName, userId);
484         } else {
485             Log.w(TAG, "Failed to clear data!");
486             mClearDataPreference.refresh();
487         }
488     }
489 
clearCache()490     public void clearCache() {
491         mClearCachePreference.setClearingCache(true);
492         mPackageManager.deleteApplicationCacheFiles(mEntry.info.packageName,
493                 new IPackageDataObserver.Stub() {
494                     public void onRemoveCompleted(final String packageName,
495                             final boolean succeeded) {
496                         mHandler.post(new Runnable() {
497                             @Override
498                             public void run() {
499                                 mClearCachePreference.setClearingCache(false);
500                                 cacheCleared(succeeded);
501                             }
502                         });
503                     }
504                 });
505         mClearCachePreference.refresh();
506     }
507 
cacheCleared(boolean succeeded)508     private void cacheCleared(boolean succeeded) {
509         if (succeeded) {
510             final int userId =  UserHandle.getUserId(mEntry.info.uid);
511             mApplicationsState.requestSize(mPackageName, userId);
512         } else {
513             Log.w(TAG, "Failed to clear cache!");
514             mClearCachePreference.refresh();
515         }
516     }
517 
518     /**
519      * Clearing data can only be disabled for system apps. For all non-system apps it is enabled.
520      * System apps disable it explicitly via the android:allowClearUserData tag.
521      **/
clearDataAllowed()522     private boolean clearDataAllowed() {
523         boolean sysApp = (mEntry.info.flags & FLAG_SYSTEM) == FLAG_SYSTEM;
524         boolean allowClearData =
525                 (mEntry.info.flags & FLAG_ALLOW_CLEAR_USER_DATA) == FLAG_ALLOW_CLEAR_USER_DATA;
526         return !sysApp || allowClearData;
527     }
528 
529     @Override
getPageId()530     protected int getPageId() {
531         return TvSettingsEnums.APPS_ALL_APPS_APP_ENTRY;
532     }
533 
534     private class ApplicationsStateCallbacks implements ApplicationsState.Callbacks {
535 
536         @Override
onRunningStateChanged(boolean running)537         public void onRunningStateChanged(boolean running) {
538             if (mForceStopPreference != null) {
539                 mForceStopPreference.refresh();
540             }
541         }
542 
543         @Override
onPackageListChanged()544         public void onPackageListChanged() {
545             if (mEntry == null || mEntry.info == null) {
546                 return;
547             }
548             final int userId = UserHandle.getUserId(mEntry.info.uid);
549             mEntry = mApplicationsState.getEntry(mPackageName, userId);
550             if (mEntry == null) {
551                 navigateBack();
552             }
553             updatePrefs();
554         }
555 
556         @Override
onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)557         public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {}
558 
559         @Override
onPackageIconChanged()560         public void onPackageIconChanged() {}
561 
562         @Override
onPackageSizeChanged(String packageName)563         public void onPackageSizeChanged(String packageName) {
564             if (mAppStoragePreference == null) {
565                 // Nothing to do here.
566                 return;
567             }
568             mAppStoragePreference.refresh();
569             mClearCachePreference.refresh();
570 
571             if (mClearDataPreference != null) {
572                 mClearDataPreference.refresh();
573             }
574         }
575 
576         @Override
onAllSizesComputed()577         public void onAllSizesComputed() {
578             if (mAppStoragePreference == null) {
579                 // Nothing to do here.
580                 return;
581             }
582             mAppStoragePreference.refresh();
583             mClearCachePreference.refresh();
584 
585             if (mClearDataPreference != null) {
586                 mClearDataPreference.refresh();
587             }
588         }
589 
590         @Override
onLauncherInfoChanged()591         public void onLauncherInfoChanged() {
592             updatePrefs();
593         }
594 
595         @Override
onLoadEntriesCompleted()596         public void onLoadEntriesCompleted() {
597             mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId());
598             updatePrefs();
599             if (mAppStoragePreference == null) {
600                 // Nothing to do here.
601                 return;
602             }
603             mAppStoragePreference.refresh();
604             mClearCachePreference.refresh();
605 
606             if (mClearDataPreference != null) {
607                 mClearDataPreference.refresh();
608             }
609         }
610     }
611 }
612