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