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