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