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 */ 16package com.android.car.settings.applications; 17 18import android.app.Activity; 19import android.app.ActivityManager; 20import android.app.admin.DevicePolicyManager; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.ApplicationInfo; 25import android.content.pm.PackageInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.icu.text.ListFormatter; 29import android.net.Uri; 30import android.os.Bundle; 31import android.os.UserHandle; 32import android.util.Log; 33import android.view.View; 34import android.widget.TextView; 35 36import com.android.car.settings.R; 37 38import com.android.car.settings.common.ListSettingsFragment; 39import com.android.car.settings.common.SingleTextLineItem; 40import com.android.car.settings.common.TypedPagedListAdapter; 41import com.android.settingslib.Utils; 42import com.android.settingslib.applications.PermissionsSummaryHelper; 43import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback; 44 45import java.text.MessageFormat; 46import java.util.ArrayList; 47import java.util.List; 48 49/** 50 * Shows details about an application and action associated with that application, 51 * like uninstall, forceStop. 52 */ 53public 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