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