Home | History | Annotate | Download | only in applications
      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 package com.android.car.settings.applications;
     17 
     18 import android.app.Activity;
     19 import android.app.ActivityManager;
     20 import android.app.admin.DevicePolicyManager;
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.PackageInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.ResolveInfo;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.UserHandle;
     31 import android.view.View;
     32 import android.widget.Button;
     33 
     34 import androidx.car.widget.ListItem;
     35 import androidx.car.widget.ListItemProvider;
     36 import androidx.car.widget.TextListItem;
     37 
     38 import com.android.car.settings.R;
     39 import com.android.car.settings.common.ListItemSettingsFragment;
     40 import com.android.car.settings.common.Logger;
     41 import com.android.settingslib.Utils;
     42 
     43 import java.text.MessageFormat;
     44 import java.util.ArrayList;
     45 import java.util.List;
     46 
     47 /**
     48  * Shows details about an application and action associated with that application,
     49  * like uninstall, forceStop.
     50  */
     51 public class ApplicationDetailFragment extends ListItemSettingsFragment {
     52     private static final Logger LOG = new Logger(ApplicationDetailFragment.class);
     53     public static final String EXTRA_RESOLVE_INFO = "extra_resolve_info";
     54 
     55     private ResolveInfo mResolveInfo;
     56     private PackageInfo mPackageInfo;
     57 
     58     private Button mDisableToggle;
     59     private Button mForceStopButton;
     60     private DevicePolicyManager mDpm;
     61 
     62     public static ApplicationDetailFragment getInstance(ResolveInfo resolveInfo) {
     63         ApplicationDetailFragment applicationDetailFragment = new ApplicationDetailFragment();
     64         Bundle bundle = ListItemSettingsFragment.getBundle();
     65         bundle.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo);
     66         bundle.putInt(EXTRA_TITLE_ID, R.string.applications_settings);
     67         bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
     68         applicationDetailFragment.setArguments(bundle);
     69         return applicationDetailFragment;
     70     }
     71 
     72     @Override
     73     public void onCreate(Bundle savedInstanceState) {
     74         super.onCreate(savedInstanceState);
     75         mResolveInfo = getArguments().getParcelable(EXTRA_RESOLVE_INFO);
     76     }
     77 
     78     @Override
     79     public void onActivityCreated(Bundle savedInstanceState) {
     80         mPackageInfo = getPackageInfo();
     81         super.onActivityCreated(savedInstanceState);
     82         if (mResolveInfo == null) {
     83             LOG.w("No application info set.");
     84             return;
     85         }
     86 
     87         mDisableToggle = (Button) getActivity().findViewById(R.id.action_button1);
     88         mForceStopButton = (Button) getActivity().findViewById(R.id.action_button2);
     89         mForceStopButton.setText(R.string.force_stop);
     90         mForceStopButton.setVisibility(View.VISIBLE);
     91 
     92         mDpm = (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
     93         updateForceStopButton();
     94         mForceStopButton.setOnClickListener(
     95                 v -> forceStopPackage(mResolveInfo.activityInfo.packageName));
     96     }
     97 
     98     @Override
     99     public void onStart() {
    100         super.onStart();
    101         updateForceStopButton();
    102         updateDisableable();
    103     }
    104 
    105     @Override
    106     public ListItemProvider getItemProvider() {
    107         return new ListItemProvider.ListProvider(getListItems());
    108     }
    109 
    110     private List<ListItem> getListItems() {
    111         ArrayList<ListItem> items = new ArrayList<>();
    112         items.add(new ApplicationLineItem(
    113                 getContext(),
    114                 getContext().getPackageManager(),
    115                 mResolveInfo,
    116                 /* fragmentController= */ null,
    117                 false));
    118         items.add(new ApplicationPermissionLineItem(getContext(), mResolveInfo, this));
    119         TextListItem versionItem = new TextListItem(getContext());
    120         versionItem.setTitle(getContext().getString(
    121                 R.string.application_version_label, mPackageInfo.versionName));
    122         items.add(versionItem);
    123         return items;
    124     }
    125 
    126     // fetch the latest ApplicationInfo instead of caching it so it reflects the current state.
    127     private ApplicationInfo getAppInfo() {
    128         try {
    129             return getContext().getPackageManager().getApplicationInfo(
    130                     mResolveInfo.activityInfo.packageName, 0 /* flag */);
    131         } catch (PackageManager.NameNotFoundException e) {
    132             LOG.e("incorrect packagename: " + mResolveInfo.activityInfo.packageName, e);
    133             throw new IllegalArgumentException(e);
    134         }
    135     }
    136 
    137     private PackageInfo getPackageInfo() {
    138         try {
    139             return getContext().getPackageManager().getPackageInfo(
    140                     mResolveInfo.activityInfo.packageName, 0 /* flag */);
    141         } catch (PackageManager.NameNotFoundException e) {
    142             LOG.e("incorrect packagename: " + mResolveInfo.activityInfo.packageName, e);
    143             throw new IllegalArgumentException(e);
    144         }
    145     }
    146 
    147     private void updateDisableable() {
    148         boolean disableable = false;
    149         boolean disabled = false;
    150         // Try to prevent the user from bricking their phone
    151         // by not allowing disabling of apps in the system.
    152         if (Utils.isSystemPackage(
    153                 getResources(), getContext().getPackageManager(), mPackageInfo)) {
    154             // Disable button for core system applications.
    155             mDisableToggle.setText(R.string.disable_text);
    156             disabled = false;
    157         } else if (getAppInfo().enabled && !isDisabledUntilUsed()) {
    158             mDisableToggle.setText(R.string.disable_text);
    159             disableable = true;
    160             disabled = false;
    161         } else {
    162             mDisableToggle.setText(R.string.enable_text);
    163             disableable = true;
    164             disabled = true;
    165         }
    166         mDisableToggle.setEnabled(disableable);
    167         final int enableState = disabled
    168                 ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
    169                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
    170         mDisableToggle.setOnClickListener(v -> {
    171             getContext().getPackageManager().setApplicationEnabledSetting(
    172                     mResolveInfo.activityInfo.packageName,
    173                     enableState,
    174                     0);
    175             updateDisableable();
    176         });
    177     }
    178 
    179     private boolean isDisabledUntilUsed() {
    180         return getAppInfo().enabledSetting
    181                 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
    182     }
    183 
    184     private void forceStopPackage(String pkgName) {
    185         ActivityManager am = (ActivityManager) getContext().getSystemService(
    186                 Context.ACTIVITY_SERVICE);
    187         LOG.d("Stopping package " + pkgName);
    188         am.forceStopPackage(pkgName);
    189         updateForceStopButton();
    190     }
    191 
    192     // enable or disable the force stop button:
    193     // - disabled if it's a device admin
    194     // - if the application is stopped unexplicitly, enabled the button
    195     // - if there's a reason for the system to restart the application, that indicates the app
    196     //   can be force stopped.
    197     private void updateForceStopButton() {
    198         if (mDpm.packageHasActiveAdmins(mResolveInfo.activityInfo.packageName)) {
    199             // User can't force stop device admin.
    200             LOG.d("Disabling button, user can't force stop device admin");
    201             mForceStopButton.setEnabled(false);
    202         } else if ((getAppInfo().flags & ApplicationInfo.FLAG_STOPPED) == 0) {
    203             // If the app isn't explicitly stopped, then always show the
    204             // force stop button.
    205             LOG.w("App is not explicitly stopped");
    206             mForceStopButton.setEnabled(true);
    207         } else {
    208             Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
    209                     Uri.fromParts("package", mResolveInfo.activityInfo.packageName, null));
    210             intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{
    211                     mResolveInfo.activityInfo.packageName
    212             });
    213             LOG.d("Sending broadcast to query restart for "
    214                     + mResolveInfo.activityInfo.packageName);
    215             getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
    216                     mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
    217         }
    218     }
    219 
    220     private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
    221         @Override
    222         public void onReceive(Context context, Intent intent) {
    223             final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
    224             LOG.d(MessageFormat.format("Got broadcast response: Restart status for {0} {1}",
    225                     mResolveInfo.activityInfo.packageName, enabled));
    226             mForceStopButton.setEnabled(enabled);
    227         }
    228     };
    229 }
    230