• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.appinfo;
18 
19 import static com.android.settings.core.instrumentation.SettingsStatsLog.AUTO_REVOKED_APP_INTERACTION;
20 import static com.android.settings.core.instrumentation.SettingsStatsLog.AUTO_REVOKED_APP_INTERACTION__ACTION__OPEN_IN_SETTINGS;
21 import static com.android.settings.core.instrumentation.SettingsStatsLog.AUTO_REVOKED_APP_INTERACTION__ACTION__REMOVE_IN_SETTINGS;
22 
23 import android.app.Activity;
24 import android.app.ActivityManager;
25 import android.app.admin.DevicePolicyManager;
26 import android.app.settings.SettingsEnums;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.om.OverlayInfo;
33 import android.content.om.OverlayManager;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.Flags;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.res.Resources;
40 import android.net.Uri;
41 import android.os.AsyncTask;
42 import android.os.Bundle;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.util.Log;
46 import android.view.View;
47 
48 import androidx.annotation.VisibleForTesting;
49 import androidx.fragment.app.Fragment;
50 import androidx.preference.PreferenceScreen;
51 
52 import com.android.settings.R;
53 import com.android.settings.SettingsActivity;
54 import com.android.settings.Utils;
55 import com.android.settings.applications.ApplicationFeatureProvider;
56 import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
57 import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd;
58 import com.android.settings.core.BasePreferenceController;
59 import com.android.settings.core.InstrumentedPreferenceFragment;
60 import com.android.settings.core.PreferenceControllerMixin;
61 import com.android.settings.core.instrumentation.SettingsStatsLog;
62 import com.android.settings.overlay.FeatureFactory;
63 import com.android.settingslib.RestrictedLockUtils;
64 import com.android.settingslib.RestrictedLockUtilsInternal;
65 import com.android.settingslib.applications.AppUtils;
66 import com.android.settingslib.applications.ApplicationsState;
67 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
68 import com.android.settingslib.core.lifecycle.Lifecycle;
69 import com.android.settingslib.core.lifecycle.LifecycleObserver;
70 import com.android.settingslib.core.lifecycle.events.OnDestroy;
71 import com.android.settingslib.core.lifecycle.events.OnResume;
72 import com.android.settingslib.widget.ActionButtonsPreference;
73 
74 import java.util.ArrayList;
75 import java.util.HashSet;
76 import java.util.List;
77 
78 /**
79  * Controller to control the uninstall button and forcestop button. All fragments that use
80  * this controller should implement {@link ButtonActionDialogFragment.AppButtonsDialogListener} and
81  * handle {@link Fragment#onActivityResult(int, int, Intent)}
82  *
83  * An easy way to handle them is to delegate them to {@link #handleDialogClick(int)} and
84  * {@link #handleActivityResult(int, int, Intent)} in this controller.
85  */
86 public class AppButtonsPreferenceController extends BasePreferenceController implements
87         PreferenceControllerMixin, LifecycleObserver, OnResume, OnDestroy,
88         ApplicationsState.Callbacks {
89     public static final String APP_CHG = "chg";
90     public static final String KEY_REMOVE_TASK_WHEN_FINISHING = "remove_task_when_finishing";
91 
92     private static final String TAG = "AppButtonsPrefCtl";
93     private static final String KEY_ACTION_BUTTONS = "action_buttons";
94     private static final boolean LOCAL_LOGV = false;
95 
96     @VisibleForTesting
97     final HashSet<String> mHomePackages = new HashSet<>();
98     @VisibleForTesting
99     ApplicationsState mState;
100     @VisibleForTesting
101     ApplicationsState.AppEntry mAppEntry;
102     @VisibleForTesting
103     PackageInfo mPackageInfo;
104     @VisibleForTesting
105     String mPackageName;
106     @VisibleForTesting
107     ActionButtonsPreference mButtonsPref;
108 
109     private final int mUserId;
110     private final int mRequestUninstall;
111     private final int mRequestRemoveDeviceAdmin;
112     private final DevicePolicyManager mDpm;
113     private final UserManager mUserManager;
114     private final OverlayManager mOverlayManager;
115     private final PackageManager mPm;
116     private final SettingsActivity mActivity;
117     private final InstrumentedPreferenceFragment mFragment;
118     private final MetricsFeatureProvider mMetricsFeatureProvider;
119     private final ApplicationFeatureProvider mApplicationFeatureProvider;
120 
121     private Intent mAppLaunchIntent;
122     private ApplicationsState.Session mSession;
123     private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin;
124     private PreferenceScreen mScreen;
125 
126     private long mSessionId;
127     private boolean mListeningToPackageRemove = false;
128     private boolean mFinishing = false;
129     private boolean mAppsControlDisallowedBySystem;
130     private boolean mAccessedFromAutoRevoke;
131 
AppButtonsPreferenceController(SettingsActivity activity, InstrumentedPreferenceFragment fragment, Lifecycle lifecycle, String packageName, ApplicationsState state, int requestUninstall, int requestRemoveDeviceAdmin)132     public AppButtonsPreferenceController(SettingsActivity activity,
133             InstrumentedPreferenceFragment fragment,
134             Lifecycle lifecycle, String packageName, ApplicationsState state,
135             int requestUninstall, int requestRemoveDeviceAdmin) {
136         super(activity, KEY_ACTION_BUTTONS);
137 
138         if (!(fragment instanceof ButtonActionDialogFragment.AppButtonsDialogListener)) {
139             throw new IllegalArgumentException(
140                     "Fragment should implement AppButtonsDialogListener");
141         }
142 
143         final FeatureFactory factory = FeatureFactory.getFeatureFactory();
144         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
145         mApplicationFeatureProvider = factory.getApplicationFeatureProvider();
146         mState = state;
147         mDpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
148         mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
149         mPm = activity.getPackageManager();
150         mOverlayManager = activity.getSystemService(OverlayManager.class);
151         mPackageName = packageName;
152         mActivity = activity;
153         mFragment = fragment;
154         mUserId = UserHandle.myUserId();
155         mRequestUninstall = requestUninstall;
156         mRequestRemoveDeviceAdmin = requestRemoveDeviceAdmin;
157         mAppLaunchIntent = mPm.getLaunchIntentForPackage(mPackageName);
158         mSessionId = activity.getIntent().getLongExtra(Intent.ACTION_AUTO_REVOKE_PERMISSIONS, 0);
159         mAccessedFromAutoRevoke = mSessionId != 0;
160 
161         if (packageName != null) {
162             mAppEntry = mState.getEntry(packageName, mUserId);
163             mSession = mState.newSession(this, lifecycle);
164             lifecycle.addObserver(this);
165         } else {
166             mFinishing = true;
167         }
168     }
169 
170     @Override
getAvailabilityStatus()171     public int getAvailabilityStatus() {
172         // TODO(b/37313605): Re-enable once this controller supports instant apps
173         return mFinishing || isInstantApp() || isSystemModule() ? DISABLED_FOR_USER : AVAILABLE;
174     }
175 
176     @Override
displayPreference(PreferenceScreen screen)177     public void displayPreference(PreferenceScreen screen) {
178         super.displayPreference(screen);
179         mScreen = screen;
180         if (isAvailable()) {
181             initButtonPreference();
182         }
183     }
184 
185     @Override
getPreferenceKey()186     public String getPreferenceKey() {
187         return KEY_ACTION_BUTTONS;
188     }
189 
190     @Override
onResume()191     public void onResume() {
192         if (isAvailable()) {
193             mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction(
194                     mActivity, UserManager.DISALLOW_APPS_CONTROL, mUserId);
195             mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
196                     mActivity, UserManager.DISALLOW_APPS_CONTROL, mUserId);
197 
198             if (!refreshUi()) {
199                 setIntentAndFinish(false);
200             }
201         }
202     }
203 
204     @Override
onDestroy()205     public void onDestroy() {
206         stopListeningToPackageRemove();
207     }
208 
209     private class UninstallAndDisableButtonListener implements View.OnClickListener {
210 
211         @Override
onClick(View v)212         public void onClick(View v) {
213             if (mAccessedFromAutoRevoke) {
214 
215                 Log.i(TAG, "sessionId: " + mSessionId + " uninstalling " + mPackageName
216                         + " with uid " + getUid() + ", reached from auto revoke");
217                 SettingsStatsLog.write(AUTO_REVOKED_APP_INTERACTION, mSessionId, getUid(),
218                         mPackageName, AUTO_REVOKED_APP_INTERACTION__ACTION__REMOVE_IN_SETTINGS);
219             }
220             final String packageName = mAppEntry.info.packageName;
221             // Uninstall
222             if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
223                 stopListeningToPackageRemove();
224                 Intent uninstallDaIntent = new Intent(mActivity, DeviceAdminAdd.class);
225                 uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
226                         packageName);
227                 mMetricsFeatureProvider.action(mActivity,
228                         SettingsEnums.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN,
229                         getPackageNameForMetric());
230                 mFragment.startActivityForResult(uninstallDaIntent, mRequestRemoveDeviceAdmin);
231                 return;
232             }
233             RestrictedLockUtils.EnforcedAdmin admin =
234                     RestrictedLockUtilsInternal.checkIfUninstallBlocked(mActivity,
235                             packageName, mUserId);
236             boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
237                     RestrictedLockUtilsInternal.hasBaseUserRestriction(mActivity, packageName,
238                             mUserId);
239             if (admin != null && !uninstallBlockedBySystem) {
240                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, admin);
241             } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
242                 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
243                     showDialogInner(ButtonActionDialogFragment.DialogType.DISABLE);
244                 } else if (mAppEntry.info.enabled) {
245                     requireAuthAndExecute(() -> {
246                         mMetricsFeatureProvider.action(
247                                 mActivity,
248                                 SettingsEnums.ACTION_SETTINGS_DISABLE_APP,
249                                 getPackageNameForMetric());
250                         AsyncTask.execute(new DisableChangerRunnable(mPm,
251                                 mAppEntry.info.packageName,
252                                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT));
253                     });
254                 } else {
255                     mMetricsFeatureProvider.action(
256                             mActivity,
257                             SettingsEnums.ACTION_SETTINGS_ENABLE_APP,
258                             getPackageNameForMetric());
259                     AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
260                             PackageManager.COMPONENT_ENABLED_STATE_DEFAULT));
261                 }
262             } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
263                 uninstallPkg(packageName, true);
264             } else {
265                 uninstallPkg(packageName, false);
266             }
267         }
268     }
269 
270     private class ForceStopButtonListener implements View.OnClickListener {
271 
272         @Override
onClick(View v)273         public void onClick(View v) {
274              mMetricsFeatureProvider.action(
275                      mActivity,
276                      SettingsEnums.ACTION_APP_INFO_FORCE_STOP,
277                      getPackageNameForMetric());
278             // force stop
279             if (mPm.isPackageStateProtected(mAppEntry.info.packageName, mUserId)) {
280                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity,
281                         RestrictedLockUtilsInternal.getDeviceOwner(mActivity));
282                 return;
283             }
284             if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
285                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
286                         mActivity, mAppsControlDisallowedAdmin);
287             } else {
288                 showDialogInner(ButtonActionDialogFragment.DialogType.FORCE_STOP);
289             }
290         }
291     }
292 
handleActivityResult(int requestCode, int resultCode, Intent data)293     public void handleActivityResult(int requestCode, int resultCode, Intent data) {
294         if (requestCode == mRequestUninstall) {
295             refreshAndFinishIfPossible(true);
296         } else if (requestCode == mRequestRemoveDeviceAdmin) {
297             refreshAndFinishIfPossible(false);
298         }
299     }
300 
301     /**
302      * Runs the given action with restricted lock authentication if it is a protected package.
303      *
304      * @param action The action to run.
305      */
requireAuthAndExecute(Runnable action)306     private void requireAuthAndExecute(Runnable action) {
307         if (Utils.isProtectedPackage(mContext, mAppEntry.info.packageName)) {
308             AppInfoDashboardFragment.showLockScreen(mContext, () -> action.run());
309         } else {
310             action.run();
311         }
312     }
313 
handleDialogClick(int id)314     public void handleDialogClick(int id) {
315         switch (id) {
316             case ButtonActionDialogFragment.DialogType.DISABLE:
317                 requireAuthAndExecute(() -> {
318                     mMetricsFeatureProvider.action(mActivity,
319                             SettingsEnums.ACTION_SETTINGS_DISABLE_APP,
320                             getPackageNameForMetric());
321                     AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
322                             PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
323                 });
324                 break;
325             case ButtonActionDialogFragment.DialogType.FORCE_STOP:
326                 requireAuthAndExecute(() -> {
327                     forceStopPackage(mAppEntry.info.packageName);
328                 });
329                 break;
330         }
331     }
332 
333     @Override
onRunningStateChanged(boolean running)334     public void onRunningStateChanged(boolean running) {
335 
336     }
337 
338     @Override
onPackageListChanged()339     public void onPackageListChanged() {
340         if (isAvailable()) {
341             refreshUi();
342         }
343     }
344 
345     @Override
onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)346     public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
347 
348     }
349 
350     @Override
onPackageIconChanged()351     public void onPackageIconChanged() {
352 
353     }
354 
355     @Override
onPackageSizeChanged(String packageName)356     public void onPackageSizeChanged(String packageName) {
357 
358     }
359 
360     @Override
onAllSizesComputed()361     public void onAllSizesComputed() {
362 
363     }
364 
365     @Override
onLauncherInfoChanged()366     public void onLauncherInfoChanged() {
367 
368     }
369 
370     @Override
onLoadEntriesCompleted()371     public void onLoadEntriesCompleted() {
372 
373     }
374 
375     @VisibleForTesting
retrieveAppEntry()376     void retrieveAppEntry() {
377         mAppEntry = mState.getEntry(mPackageName, mUserId);
378         if (mAppEntry != null) {
379             try {
380                 mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName,
381                         PackageManager.MATCH_DISABLED_COMPONENTS |
382                                 PackageManager.MATCH_ANY_USER |
383                                 PackageManager.GET_SIGNATURES |
384                                 PackageManager.GET_PERMISSIONS);
385 
386                 mPackageName = mAppEntry.info.packageName;
387             } catch (PackageManager.NameNotFoundException e) {
388                 Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
389                 mPackageInfo = null;
390             }
391         } else {
392             mPackageInfo = null;
393         }
394     }
395 
396     @VisibleForTesting
updateOpenButton()397     void updateOpenButton() {
398         mAppLaunchIntent = mPm.getLaunchIntentForPackage(mPackageName);
399         mButtonsPref.setButton1Visible(mAppLaunchIntent != null);
400     }
401 
402     @VisibleForTesting
updateUninstallButton()403     void updateUninstallButton() {
404         final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
405         boolean enabled = true;
406         if (isBundled) {
407             enabled = handleDisableable();
408         } else {
409             if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
410                     && mUserManager.getUsers().size() >= 2) {
411                 // When we have multiple users, there is a separate menu
412                 // to uninstall for all users.
413                 enabled = false;
414             }
415         }
416         // If this is a device admin, it can't be uninstalled or disabled.
417         // We do this here so the text of the button is still set correctly.
418         if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
419             enabled = false;
420         }
421 
422         // We don't allow uninstalling DO/PO on *any* users if it's a system app, because
423         // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
424         // will clear data on all users.
425         if (isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) {
426             if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) {
427                 enabled = false;
428             }
429         // We allow uninstalling if the calling user is not a DO/PO and if it's not a system app,
430         // because this will not have device-wide consequences.
431         } else {
432             if (Utils.isProfileOrDeviceOwner(mDpm, mPackageInfo.packageName, mUserId)) {
433                 enabled = false;
434             }
435         }
436 
437         // Don't allow uninstalling the device provisioning package.
438         if (Utils.isDeviceProvisioningPackage(mContext.getResources(),
439                 mAppEntry.info.packageName)) {
440             enabled = false;
441         }
442 
443         // If the uninstall intent is already queued, disable the uninstall button
444         if (mDpm.isUninstallInQueue(mPackageName)) {
445             enabled = false;
446         }
447 
448         // Home apps need special handling.  Bundled ones we don't risk downgrading
449         // because that can interfere with home-key resolution.  Furthermore, we
450         // can't allow uninstallation of the only home app, and we don't want to
451         // allow uninstallation of an explicitly preferred one -- the user can go
452         // to Home settings and pick a different one, after which we'll permit
453         // uninstallation of the now-not-default one.
454         if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
455             if (isBundled) {
456                 enabled = false;
457             } else {
458                 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
459                 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
460                 if (currentDefaultHome == null) {
461                     // No preferred default, so permit uninstall only when
462                     // there is more than one candidate
463                     enabled = (mHomePackages.size() > 1);
464                 } else if (mPackageInfo.packageName.equals(currentDefaultHome.getPackageName())) {
465                     if (Flags.improveHomeAppBehavior()) {
466                         // Allow uninstallation of current home app if it is a non-system app
467                         // and/or there are other candidate apps available.
468                         if (mPackageInfo.applicationInfo.isSystemApp()
469                                 || mHomePackages.size() == 1) {
470                             enabled = false;
471                         }
472                     } else {
473                         enabled = false;
474                     }
475                 }
476             }
477         }
478 
479         if (mAppsControlDisallowedBySystem) {
480             enabled = false;
481         }
482 
483         // Resource overlays can be uninstalled iff they are public
484         // (installed on /data) and disabled. ("Enabled" means they
485         // are in use by resource management.)  If they are
486         // system/vendor, they can never be uninstalled. :-(
487         if (mAppEntry.info.isResourceOverlay()) {
488             if (isBundled) {
489                 enabled = false;
490             } else {
491                 String pkgName = mAppEntry.info.packageName;
492                 UserHandle user = UserHandle.getUserHandleForUid(mAppEntry.info.uid);
493                 OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(pkgName, user);
494                 if (overlayInfo != null && overlayInfo.isEnabled()) {
495                     ApplicationsState.AppEntry targetEntry =
496                             mState.getEntry(overlayInfo.targetPackageName,
497                                             UserHandle.getUserId(mAppEntry.info.uid));
498                     if (targetEntry != null) {
499                         enabled = false;
500                     }
501                 }
502             }
503         }
504 
505         mButtonsPref.setButton2Enabled(enabled);
506     }
507 
508     /**
509      * Finish this fragment and return data if possible
510      */
setIntentAndFinish(boolean removeTaskWhenFinishing)511     private void setIntentAndFinish(boolean removeTaskWhenFinishing) {
512         Intent intent = new Intent();
513         intent.putExtra(APP_CHG, true);
514         intent.putExtra(KEY_REMOVE_TASK_WHEN_FINISHING, removeTaskWhenFinishing);
515         mActivity.finishPreferencePanel(Activity.RESULT_OK, intent);
516         mFinishing = true;
517     }
518 
refreshAndFinishIfPossible(boolean removeTaskWhenFinishing)519     private void refreshAndFinishIfPossible(boolean removeTaskWhenFinishing) {
520         if (!refreshUi()) {
521             setIntentAndFinish(removeTaskWhenFinishing);
522         } else {
523             startListeningToPackageRemove();
524         }
525     }
526 
527     @VisibleForTesting
updateForceStopButton()528     void updateForceStopButton() {
529         if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
530             // User can't force stop device admin.
531             Log.w(TAG, "User can't force stop device admin");
532             updateForceStopButtonInner(false /* enabled */);
533         } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
534             // If the app isn't explicitly stopped, then always show the
535             // force stop button.
536             Log.w(TAG, "App is not explicitly stopped");
537             updateForceStopButtonInner(true /* enabled */);
538         } else {
539             Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
540                     Uri.fromParts("package", mAppEntry.info.packageName, null));
541             intent.setPackage("android");
542             intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{mAppEntry.info.packageName});
543             intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
544             intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
545             Log.d(TAG, "Sending broadcast to query restart status for "
546                     + mAppEntry.info.packageName);
547             mActivity.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
548                     android.Manifest.permission.HANDLE_QUERY_PACKAGE_RESTART,
549                     mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
550         }
551     }
552 
553     @VisibleForTesting
updateForceStopButtonInner(boolean enabled)554     void updateForceStopButtonInner(boolean enabled) {
555         if (mAppsControlDisallowedBySystem) {
556             mButtonsPref.setButton3Enabled(false);
557         } else {
558             mButtonsPref.setButton3Enabled(enabled);
559         }
560     }
561 
562     @VisibleForTesting
uninstallPkg(String packageName, boolean allUsers)563     void uninstallPkg(String packageName, boolean allUsers) {
564         requireAuthAndExecute(() -> {
565             stopListeningToPackageRemove();
566             // Create new intent to launch Uninstaller activity
567             Uri packageUri = Uri.parse("package:" + packageName);
568             Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
569             uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
570 
571             mMetricsFeatureProvider.action(mActivity, SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP);
572             mFragment.startActivityForResult(uninstallIntent, mRequestUninstall);
573         });
574     }
575 
576     @VisibleForTesting
forceStopPackage(String pkgName)577     void forceStopPackage(String pkgName) {
578         mMetricsFeatureProvider.action(
579                 mMetricsFeatureProvider.getAttribution(mActivity),
580                 SettingsEnums.ACTION_APP_FORCE_STOP,
581                 mFragment.getMetricsCategory(),
582                 pkgName,
583                 0);
584         ActivityManager am = (ActivityManager) mActivity.getSystemService(
585                 Context.ACTIVITY_SERVICE);
586         Log.d(TAG, "Stopping package " + pkgName);
587         if (android.app.Flags.appRestrictionsApi()) {
588             am.noteAppRestrictionEnabled(pkgName, mAppEntry.info.uid,
589                     ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true,
590                     ActivityManager.RESTRICTION_REASON_USER,
591                     "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L);
592         }
593         am.forceStopPackage(pkgName);
594         int userId = UserHandle.getUserId(mAppEntry.info.uid);
595         mState.invalidatePackage(pkgName, userId);
596         ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId);
597         if (newEnt != null) {
598             mAppEntry = newEnt;
599         }
600         updateForceStopButton();
601     }
602 
603     @VisibleForTesting
handleDisableable()604     boolean handleDisableable() {
605         boolean disableable = false;
606         // Try to prevent the user from bricking their phone
607         // by not allowing disabling of apps signed with the
608         // system cert and any launcher app in the system.
609         if (mHomePackages.contains(mAppEntry.info.packageName)
610                 || isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) {
611             // Disable button for core system applications.
612             mButtonsPref.setButton2Text(R.string.disable_text)
613                     .setButton2Icon(R.drawable.ic_settings_disable);
614         } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
615             mButtonsPref.setButton2Text(R.string.disable_text)
616                     .setButton2Icon(R.drawable.ic_settings_disable);
617             disableable = !mApplicationFeatureProvider.getKeepEnabledPackages()
618                     .contains(mAppEntry.info.packageName);
619         } else {
620             mButtonsPref.setButton2Text(R.string.enable_text)
621                     .setButton2Icon(R.drawable.ic_settings_enable);
622             disableable = true;
623         }
624 
625         return disableable;
626     }
627 
628     @VisibleForTesting
isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo)629     boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) {
630         return Utils.isSystemPackage(resources, pm, packageInfo);
631     }
632 
isDisabledUntilUsed()633     private boolean isDisabledUntilUsed() {
634         return mAppEntry.info.enabledSetting
635                 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
636     }
637 
showDialogInner(@uttonActionDialogFragment.DialogType int id)638     private void showDialogInner(@ButtonActionDialogFragment.DialogType int id) {
639         ButtonActionDialogFragment newFragment = ButtonActionDialogFragment.newInstance(id);
640         newFragment.setTargetFragment(mFragment, 0);
641         newFragment.show(mActivity.getSupportFragmentManager(), "dialog " + id);
642     }
643 
644     private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
645         @Override
646         public void onReceive(Context context, Intent intent) {
647             final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
648             Log.d(TAG, "Got broadcast response: Restart status for "
649                     + mAppEntry.info.packageName + " " + enabled);
650             updateForceStopButtonInner(enabled);
651         }
652     };
653 
signaturesMatch(String pkg1, String pkg2)654     private boolean signaturesMatch(String pkg1, String pkg2) {
655         if (pkg1 != null && pkg2 != null) {
656             try {
657                 final int match = mPm.checkSignatures(pkg1, pkg2);
658                 if (match >= PackageManager.SIGNATURE_MATCH) {
659                     return true;
660                 }
661             } catch (Exception e) {
662                 // e.g. named alternate package not found during lookup;
663                 // this is an expected case sometimes
664             }
665         }
666         return false;
667     }
668 
669     @VisibleForTesting
refreshUi()670     boolean refreshUi() {
671         if (mPackageName == null) {
672             return false;
673         }
674         retrieveAppEntry();
675         if (mAppEntry == null || mPackageInfo == null) {
676             return false;
677         }
678         // Get list of "home" apps and trace through any meta-data references
679         List<ResolveInfo> homeActivities = new ArrayList<>();
680         mPm.getHomeActivities(homeActivities);
681         mHomePackages.clear();
682         for (ResolveInfo ri : homeActivities) {
683             final String activityPkg = ri.activityInfo.packageName;
684             mHomePackages.add(activityPkg);
685 
686             // Also make sure to include anything proxying for the home app
687             final Bundle metadata = ri.activityInfo.metaData;
688             if (metadata != null) {
689                 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
690                 if (signaturesMatch(metaPkg, activityPkg)) {
691                     mHomePackages.add(metaPkg);
692                 }
693             }
694         }
695 
696         // When the app was installed from instant state, buttons preferences could be null.
697         if (mButtonsPref == null) {
698             initButtonPreference();
699             mButtonsPref.setVisible(true);
700         }
701         updateOpenButton();
702         updateUninstallButton();
703         updateForceStopButton();
704 
705         return true;
706     }
707 
initButtonPreference()708     private void initButtonPreference() {
709         mButtonsPref = ((ActionButtonsPreference) mScreen.findPreference(KEY_ACTION_BUTTONS))
710                 .setButton1Text(R.string.launch_instant_app)
711                 .setButton1Icon(R.drawable.ic_settings_open)
712                 .setButton1OnClickListener(v -> launchApplication())
713                 .setButton2Text(R.string.uninstall_text)
714                 .setButton2Icon(R.drawable.ic_settings_delete)
715                 .setButton2OnClickListener(new UninstallAndDisableButtonListener())
716                 .setButton3Text(R.string.force_stop)
717                 .setButton3Icon(R.drawable.ic_settings_force_stop)
718                 .setButton3OnClickListener(new ForceStopButtonListener())
719                 .setButton3Enabled(false);
720     }
721 
startListeningToPackageRemove()722     private void startListeningToPackageRemove() {
723         if (mListeningToPackageRemove) {
724             return;
725         }
726         mListeningToPackageRemove = true;
727         final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
728         filter.addDataScheme("package");
729         mActivity.registerReceiver(mPackageRemovedReceiver, filter);
730     }
731 
stopListeningToPackageRemove()732     private void stopListeningToPackageRemove() {
733         if (!mListeningToPackageRemove) {
734             return;
735         }
736         mListeningToPackageRemove = false;
737         mActivity.unregisterReceiver(mPackageRemovedReceiver);
738     }
739 
launchApplication()740     private void launchApplication() {
741         if (mAppLaunchIntent != null) {
742             if (mAccessedFromAutoRevoke) {
743 
744                 Log.i(TAG, "sessionId: " + mSessionId + " uninstalling " + mPackageName
745                         + " with uid " + getUid() + ", reached from auto revoke");
746                 SettingsStatsLog.write(AUTO_REVOKED_APP_INTERACTION, mSessionId, getUid(),
747                         mPackageName, AUTO_REVOKED_APP_INTERACTION__ACTION__OPEN_IN_SETTINGS);
748             }
749             mContext.startActivityAsUser(mAppLaunchIntent, new UserHandle(mUserId));
750             mMetricsFeatureProvider.action(mActivity,
751                     SettingsEnums.ACTION_APP_INFO_OPEN, mPackageName);
752         }
753     }
754 
getUid()755     private int getUid() {
756         int uid = -1;
757         if (mPackageInfo == null) {
758             retrieveAppEntry();
759         }
760         if (mPackageInfo != null) {
761             uid = mPackageInfo.applicationInfo.uid;
762         }
763         return uid;
764     }
765 
isInstantApp()766     private boolean isInstantApp() {
767         return mAppEntry != null && AppUtils.isInstant(mAppEntry.info);
768     }
769 
isSystemModule()770     private boolean isSystemModule() {
771         return mAppEntry != null
772                 && (AppUtils.isSystemModule(mContext, mAppEntry.info.packageName)
773                 || AppUtils.isMainlineModule(mPm, mAppEntry.info.packageName));
774     }
775 
getPackageNameForMetric()776     private String getPackageNameForMetric() {
777         final String packageName =
778                 mAppEntry != null && mAppEntry.info != null
779                         ? mAppEntry.info.packageName
780                         : null;
781         return packageName != null ? packageName : "";
782     }
783 
784     /**
785      * Changes the status of disable/enable for a package
786      */
787     private class DisableChangerRunnable implements Runnable {
788         final PackageManager mPm;
789         final String mPackageName;
790         final int mState;
791 
DisableChangerRunnable(PackageManager pm, String packageName, int state)792         public DisableChangerRunnable(PackageManager pm, String packageName, int state) {
793             mPm = pm;
794             mPackageName = packageName;
795             mState = state;
796         }
797 
798         @Override
run()799         public void run() {
800             mPm.setApplicationEnabledSetting(mPackageName, mState, 0);
801         }
802     }
803 
804     /**
805      * Receiver to listen to the remove action for packages
806      */
807     private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
808         @Override
809         public void onReceive(Context context, Intent intent) {
810             String packageName = intent.getData().getSchemeSpecificPart();
811             if (!mFinishing && mAppEntry.info.packageName.equals(packageName)) {
812                 mActivity.finishAndRemoveTask();
813             }
814         }
815     };
816 
817 }
818