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