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                         getPackageNameForMetric());
231                 mFragment.startActivityForResult(uninstallDaIntent, mRequestRemoveDeviceAdmin);
232                 return;
233             }
234             RestrictedLockUtils.EnforcedAdmin admin =
235                     RestrictedLockUtilsInternal.checkIfUninstallBlocked(mActivity,
236                             packageName, mUserId);
237             boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
238                     RestrictedLockUtilsInternal.hasBaseUserRestriction(mActivity, packageName,
239                             mUserId);
240             if (admin != null && !uninstallBlockedBySystem) {
241                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, admin);
242             } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
243                 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
244                     // If the system app has an update and this is the only user on the device,
245                     // then offer to downgrade the app, otherwise only offer to disable the
246                     // app for this user.
247                     if (mUpdatedSysApp && isSingleUser()) {
248                         showDialogInner(ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE);
249                     } else {
250                         showDialogInner(ButtonActionDialogFragment.DialogType.DISABLE);
251                     }
252                 } else {
253                     mMetricsFeatureProvider.action(
254                             mActivity,
255                             mAppEntry.info.enabled
256                                     ? SettingsEnums.ACTION_SETTINGS_DISABLE_APP
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, false);
264             } else {
265                 uninstallPkg(packageName, false, 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             if (mDisableAfterUninstall) {
296                 mDisableAfterUninstall = false;
297                 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
298                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
299             }
300             refreshAndFinishIfPossible(true);
301         } else if (requestCode == mRequestRemoveDeviceAdmin) {
302             refreshAndFinishIfPossible(false);
303         }
304     }
305 
handleDialogClick(int id)306     public void handleDialogClick(int id) {
307         switch (id) {
308             case ButtonActionDialogFragment.DialogType.DISABLE:
309                 mMetricsFeatureProvider.action(mActivity,
310                         SettingsEnums.ACTION_SETTINGS_DISABLE_APP);
311                 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
312                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
313                 break;
314             case ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE:
315                 mMetricsFeatureProvider.action(mActivity,
316                         SettingsEnums.ACTION_SETTINGS_DISABLE_APP);
317                 uninstallPkg(mAppEntry.info.packageName, false, true);
318                 break;
319             case ButtonActionDialogFragment.DialogType.FORCE_STOP:
320                 forceStopPackage(mAppEntry.info.packageName);
321                 break;
322         }
323     }
324 
325     @Override
onRunningStateChanged(boolean running)326     public void onRunningStateChanged(boolean running) {
327 
328     }
329 
330     @Override
onPackageListChanged()331     public void onPackageListChanged() {
332         if (isAvailable()) {
333             refreshUi();
334         }
335     }
336 
337     @Override
onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)338     public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
339 
340     }
341 
342     @Override
onPackageIconChanged()343     public void onPackageIconChanged() {
344 
345     }
346 
347     @Override
onPackageSizeChanged(String packageName)348     public void onPackageSizeChanged(String packageName) {
349 
350     }
351 
352     @Override
onAllSizesComputed()353     public void onAllSizesComputed() {
354 
355     }
356 
357     @Override
onLauncherInfoChanged()358     public void onLauncherInfoChanged() {
359 
360     }
361 
362     @Override
onLoadEntriesCompleted()363     public void onLoadEntriesCompleted() {
364 
365     }
366 
367     @VisibleForTesting
retrieveAppEntry()368     void retrieveAppEntry() {
369         mAppEntry = mState.getEntry(mPackageName, mUserId);
370         if (mAppEntry != null) {
371             try {
372                 mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName,
373                         PackageManager.MATCH_DISABLED_COMPONENTS |
374                                 PackageManager.MATCH_ANY_USER |
375                                 PackageManager.GET_SIGNATURES |
376                                 PackageManager.GET_PERMISSIONS);
377 
378                 mPackageName = mAppEntry.info.packageName;
379             } catch (PackageManager.NameNotFoundException e) {
380                 Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
381                 mPackageInfo = null;
382             }
383         } else {
384             mPackageInfo = null;
385         }
386     }
387 
388     @VisibleForTesting
updateOpenButton()389     void updateOpenButton() {
390         mAppLaunchIntent = mPm.getLaunchIntentForPackage(mPackageName);
391         mButtonsPref.setButton1Visible(mAppLaunchIntent != null);
392     }
393 
394     @VisibleForTesting
updateUninstallButton()395     void updateUninstallButton() {
396         final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
397         boolean enabled = true;
398         if (isBundled) {
399             enabled = handleDisableable();
400         } else {
401             if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
402                     && mUserManager.getUsers().size() >= 2) {
403                 // When we have multiple users, there is a separate menu
404                 // to uninstall for all users.
405                 enabled = false;
406             }
407         }
408         // If this is a device admin, it can't be uninstalled or disabled.
409         // We do this here so the text of the button is still set correctly.
410         if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
411             enabled = false;
412         }
413 
414         // We don't allow uninstalling DO/PO on *any* users if it's a system app, because
415         // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
416         // will clear data on all users.
417         if (isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) {
418             if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) {
419                 enabled = false;
420             }
421         // We allow uninstalling if the calling user is not a DO/PO and if it's not a system app,
422         // because this will not have device-wide consequences.
423         } else {
424             if (Utils.isProfileOrDeviceOwner(mDpm, mPackageInfo.packageName, mUserId)) {
425                 enabled = false;
426             }
427         }
428 
429         // Don't allow uninstalling the device provisioning package.
430         if (Utils.isDeviceProvisioningPackage(mContext.getResources(),
431                 mAppEntry.info.packageName)) {
432             enabled = false;
433         }
434 
435         // If the uninstall intent is already queued, disable the uninstall button
436         if (mDpm.isUninstallInQueue(mPackageName)) {
437             enabled = false;
438         }
439 
440         // Home apps need special handling.  Bundled ones we don't risk downgrading
441         // because that can interfere with home-key resolution.  Furthermore, we
442         // can't allow uninstallation of the only home app, and we don't want to
443         // allow uninstallation of an explicitly preferred one -- the user can go
444         // to Home settings and pick a different one, after which we'll permit
445         // uninstallation of the now-not-default one.
446         if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
447             if (isBundled) {
448                 enabled = false;
449             } else {
450                 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
451                 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
452                 if (currentDefaultHome == null) {
453                     // No preferred default, so permit uninstall only when
454                     // there is more than one candidate
455                     enabled = (mHomePackages.size() > 1);
456                 } else {
457                     // There is an explicit default home app -- forbid uninstall of
458                     // that one, but permit it for installed-but-inactive ones.
459                     enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
460                 }
461             }
462         }
463 
464         if (mAppsControlDisallowedBySystem) {
465             enabled = false;
466         }
467 
468         // Resource overlays can be uninstalled iff they are public
469         // (installed on /data) and disabled. ("Enabled" means they
470         // are in use by resource management.)  If they are
471         // system/vendor, they can never be uninstalled. :-(
472         if (mAppEntry.info.isResourceOverlay()) {
473             if (isBundled) {
474                 enabled = false;
475             } else {
476                 String pkgName = mAppEntry.info.packageName;
477                 UserHandle user = UserHandle.getUserHandleForUid(mAppEntry.info.uid);
478                 OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(pkgName, user);
479                 if (overlayInfo != null && overlayInfo.isEnabled()) {
480                     ApplicationsState.AppEntry targetEntry =
481                             mState.getEntry(overlayInfo.targetPackageName,
482                                             UserHandle.getUserId(mAppEntry.info.uid));
483                     if (targetEntry != null) {
484                         enabled = false;
485                     }
486                 }
487             }
488         }
489 
490         mButtonsPref.setButton2Enabled(enabled);
491     }
492 
493     /**
494      * Finish this fragment and return data if possible
495      */
setIntentAndFinish(boolean appChanged, boolean removeTaskWhenFinishing)496     private void setIntentAndFinish(boolean appChanged, boolean removeTaskWhenFinishing) {
497         if (LOCAL_LOGV) {
498             Log.i(TAG, "appChanged=" + appChanged);
499         }
500         Intent intent = new Intent();
501         intent.putExtra(APP_CHG, appChanged);
502         intent.putExtra(KEY_REMOVE_TASK_WHEN_FINISHING, removeTaskWhenFinishing);
503         mActivity.finishPreferencePanel(Activity.RESULT_OK, intent);
504         mFinishing = true;
505     }
506 
refreshAndFinishIfPossible(boolean removeTaskWhenFinishing)507     private void refreshAndFinishIfPossible(boolean removeTaskWhenFinishing) {
508         if (!refreshUi()) {
509             setIntentAndFinish(true, removeTaskWhenFinishing);
510         } else {
511             startListeningToPackageRemove();
512         }
513     }
514 
515     @VisibleForTesting
updateForceStopButton()516     void updateForceStopButton() {
517         if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
518             // User can't force stop device admin.
519             Log.w(TAG, "User can't force stop device admin");
520             updateForceStopButtonInner(false /* enabled */);
521         } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
522             // If the app isn't explicitly stopped, then always show the
523             // force stop button.
524             Log.w(TAG, "App is not explicitly stopped");
525             updateForceStopButtonInner(true /* enabled */);
526         } else {
527             Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
528                     Uri.fromParts("package", mAppEntry.info.packageName, null));
529             intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{mAppEntry.info.packageName});
530             intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
531             intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
532             Log.d(TAG, "Sending broadcast to query restart status for "
533                     + mAppEntry.info.packageName);
534             mActivity.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
535                     mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
536         }
537     }
538 
539     @VisibleForTesting
updateForceStopButtonInner(boolean enabled)540     void updateForceStopButtonInner(boolean enabled) {
541         if (mAppsControlDisallowedBySystem) {
542             mButtonsPref.setButton3Enabled(false);
543         } else {
544             mButtonsPref.setButton3Enabled(enabled);
545         }
546     }
547 
548     @VisibleForTesting
uninstallPkg(String packageName, boolean allUsers, boolean andDisable)549     void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
550         stopListeningToPackageRemove();
551         // Create new intent to launch Uninstaller activity
552         Uri packageUri = Uri.parse("package:" + packageName);
553         Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
554         uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
555 
556         mMetricsFeatureProvider.action(
557                 mActivity, SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP);
558         mFragment.startActivityForResult(uninstallIntent, mRequestUninstall);
559         mDisableAfterUninstall = andDisable;
560     }
561 
562     @VisibleForTesting
forceStopPackage(String pkgName)563     void forceStopPackage(String pkgName) {
564         mMetricsFeatureProvider.action(
565                 mMetricsFeatureProvider.getAttribution(mActivity),
566                 SettingsEnums.ACTION_APP_FORCE_STOP,
567                 mFragment.getMetricsCategory(),
568                 pkgName,
569                 0);
570         ActivityManager am = (ActivityManager) mActivity.getSystemService(
571                 Context.ACTIVITY_SERVICE);
572         Log.d(TAG, "Stopping package " + pkgName);
573         am.forceStopPackage(pkgName);
574         int userId = UserHandle.getUserId(mAppEntry.info.uid);
575         mState.invalidatePackage(pkgName, userId);
576         ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId);
577         if (newEnt != null) {
578             mAppEntry = newEnt;
579         }
580         updateForceStopButton();
581     }
582 
583     @VisibleForTesting
handleDisableable()584     boolean handleDisableable() {
585         boolean disableable = false;
586         // Try to prevent the user from bricking their phone
587         // by not allowing disabling of apps signed with the
588         // system cert and any launcher app in the system.
589         if (mHomePackages.contains(mAppEntry.info.packageName)
590                 || isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) {
591             // Disable button for core system applications.
592             mButtonsPref.setButton2Text(R.string.disable_text)
593                     .setButton2Icon(R.drawable.ic_settings_disable);
594         } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
595             mButtonsPref.setButton2Text(R.string.disable_text)
596                     .setButton2Icon(R.drawable.ic_settings_disable);
597             disableable = !mApplicationFeatureProvider.getKeepEnabledPackages()
598                     .contains(mAppEntry.info.packageName);
599         } else {
600             mButtonsPref.setButton2Text(R.string.enable_text)
601                     .setButton2Icon(R.drawable.ic_settings_enable);
602             disableable = true;
603         }
604 
605         return disableable;
606     }
607 
608     @VisibleForTesting
isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo)609     boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) {
610         return Utils.isSystemPackage(resources, pm, packageInfo);
611     }
612 
isDisabledUntilUsed()613     private boolean isDisabledUntilUsed() {
614         return mAppEntry.info.enabledSetting
615                 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
616     }
617 
showDialogInner(@uttonActionDialogFragment.DialogType int id)618     private void showDialogInner(@ButtonActionDialogFragment.DialogType int id) {
619         ButtonActionDialogFragment newFragment = ButtonActionDialogFragment.newInstance(id);
620         newFragment.setTargetFragment(mFragment, 0);
621         newFragment.show(mActivity.getSupportFragmentManager(), "dialog " + id);
622     }
623 
624     /** Returns whether there is only one user on this device, not including the system-only user */
isSingleUser()625     private boolean isSingleUser() {
626         final int userCount = mUserManager.getUserCount();
627         return userCount == 1;
628     }
629 
630     private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
631         @Override
632         public void onReceive(Context context, Intent intent) {
633             final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
634             Log.d(TAG, "Got broadcast response: Restart status for "
635                     + mAppEntry.info.packageName + " " + enabled);
636             updateForceStopButtonInner(enabled);
637         }
638     };
639 
signaturesMatch(String pkg1, String pkg2)640     private boolean signaturesMatch(String pkg1, String pkg2) {
641         if (pkg1 != null && pkg2 != null) {
642             try {
643                 final int match = mPm.checkSignatures(pkg1, pkg2);
644                 if (match >= PackageManager.SIGNATURE_MATCH) {
645                     return true;
646                 }
647             } catch (Exception e) {
648                 // e.g. named alternate package not found during lookup;
649                 // this is an expected case sometimes
650             }
651         }
652         return false;
653     }
654 
655     @VisibleForTesting
refreshUi()656     boolean refreshUi() {
657         if (mPackageName == null) {
658             return false;
659         }
660         retrieveAppEntry();
661         if (mAppEntry == null || mPackageInfo == null) {
662             return false;
663         }
664         // Get list of "home" apps and trace through any meta-data references
665         List<ResolveInfo> homeActivities = new ArrayList<>();
666         mPm.getHomeActivities(homeActivities);
667         mHomePackages.clear();
668         for (int i = 0, size = homeActivities.size(); i < size; i++) {
669             ResolveInfo ri = homeActivities.get(i);
670             final String activityPkg = ri.activityInfo.packageName;
671             mHomePackages.add(activityPkg);
672 
673             // Also make sure to include anything proxying for the home app
674             final Bundle metadata = ri.activityInfo.metaData;
675             if (metadata != null) {
676                 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
677                 if (signaturesMatch(metaPkg, activityPkg)) {
678                     mHomePackages.add(metaPkg);
679                 }
680             }
681         }
682 
683         // When the app was installed from instant state, buttons preferences could be null.
684         if (mButtonsPref == null) {
685             initButtonPreference();
686             mButtonsPref.setVisible(true);
687         }
688         updateOpenButton();
689         updateUninstallButton();
690         updateForceStopButton();
691 
692         return true;
693     }
694 
initButtonPreference()695     private void initButtonPreference() {
696         mButtonsPref = ((ActionButtonsPreference) mScreen.findPreference(
697                 KEY_ACTION_BUTTONS))
698                 .setButton1Text(R.string.launch_instant_app)
699                 .setButton1Icon(R.drawable.ic_settings_open)
700                 .setButton1OnClickListener(v -> launchApplication())
701                 .setButton2Text(R.string.uninstall_text)
702                 .setButton2Icon(R.drawable.ic_settings_delete)
703                 .setButton2OnClickListener(new UninstallAndDisableButtonListener())
704                 .setButton3Text(R.string.force_stop)
705                 .setButton3Icon(R.drawable.ic_settings_force_stop)
706                 .setButton3OnClickListener(new ForceStopButtonListener())
707                 .setButton3Enabled(false);
708     }
709 
startListeningToPackageRemove()710     private void startListeningToPackageRemove() {
711         if (mListeningToPackageRemove) {
712             return;
713         }
714         mListeningToPackageRemove = true;
715         final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
716         filter.addDataScheme("package");
717         mActivity.registerReceiver(mPackageRemovedReceiver, filter);
718     }
719 
stopListeningToPackageRemove()720     private void stopListeningToPackageRemove() {
721         if (!mListeningToPackageRemove) {
722             return;
723         }
724         mListeningToPackageRemove = false;
725         mActivity.unregisterReceiver(mPackageRemovedReceiver);
726     }
727 
launchApplication()728     private void launchApplication() {
729         if (mAppLaunchIntent != null) {
730             if (mAccessedFromAutoRevoke) {
731 
732                 Log.i(TAG, "sessionId: " + mSessionId + " uninstalling " + mPackageName
733                         + " with uid " + getUid() + ", reached from auto revoke");
734                 SettingsStatsLog.write(AUTO_REVOKED_APP_INTERACTION, mSessionId, getUid(),
735                         mPackageName, AUTO_REVOKED_APP_INTERACTION__ACTION__OPEN_IN_SETTINGS);
736             }
737             mContext.startActivityAsUser(mAppLaunchIntent, new UserHandle(mUserId));
738             mMetricsFeatureProvider.action(mActivity,
739                     SettingsEnums.ACTION_APP_INFO_OPEN, mPackageName);
740         }
741     }
742 
getUid()743     private int getUid() {
744         int uid = -1;
745         if (mPackageInfo == null) {
746             retrieveAppEntry();
747         }
748         if (mPackageInfo != null) {
749             uid = mPackageInfo.applicationInfo.uid;
750         }
751         return uid;
752     }
753 
isInstantApp()754     private boolean isInstantApp() {
755         return mAppEntry != null && AppUtils.isInstant(mAppEntry.info);
756     }
757 
isSystemModule()758     private boolean isSystemModule() {
759         return mAppEntry != null
760                 && (AppUtils.isSystemModule(mContext, mAppEntry.info.packageName)
761                 || AppUtils.isMainlineModule(mPm, mAppEntry.info.packageName));
762     }
763 
getPackageNameForMetric()764     private String getPackageNameForMetric() {
765         final String packageName =
766                 mAppEntry != null && mAppEntry.info != null
767                         ? mAppEntry.info.packageName
768                         : null;
769         return packageName != null ? packageName : "";
770     }
771 
772     /**
773      * Changes the status of disable/enable for a package
774      */
775     private class DisableChangerRunnable implements Runnable {
776         final PackageManager mPm;
777         final String mPackageName;
778         final int mState;
779 
DisableChangerRunnable(PackageManager pm, String packageName, int state)780         public DisableChangerRunnable(PackageManager pm, String packageName, int state) {
781             mPm = pm;
782             mPackageName = packageName;
783             mState = state;
784         }
785 
786         @Override
run()787         public void run() {
788             mPm.setApplicationEnabledSetting(mPackageName, mState, 0);
789         }
790     }
791 
792     /**
793      * Receiver to listen to the remove action for packages
794      */
795     private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
796         @Override
797         public void onReceive(Context context, Intent intent) {
798             String packageName = intent.getData().getSchemeSpecificPart();
799             if (!mFinishing && mAppEntry.info.packageName.equals(packageName)) {
800                 mActivity.finishAndRemoveTask();
801             }
802         }
803     };
804 
805 }
806