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