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.ActivityNotFoundException; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.res.Resources; 30 import android.icu.text.ListFormatter; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.util.Log; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.widget.Button; 38 import android.widget.ImageView; 39 import android.widget.TextView; 40 41 import com.android.car.settings.common.CarSettingActivity; 42 import com.android.car.settings.R; 43 44 import com.android.settingslib.Utils; 45 import com.android.settingslib.applications.PermissionsSummaryHelper; 46 import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback; 47 48 import java.text.MessageFormat; 49 import java.util.ArrayList; 50 import java.util.List; 51 52 /** 53 * Shows details about an application and action associated with that application, 54 * like uninstall, forceStop. 55 */ 56 public class ApplicationDetailActivity extends CarSettingActivity { 57 private static final String TAG = "AppDetailActivity"; 58 public static final String APPLICATION_INFO_KEY = "APPLICATION_INFO_KEY"; 59 60 private ResolveInfo mResolveInfo; 61 private TextView mPermissionDetailView; 62 private View mPermissionContainer; 63 private Button mDisableToggle; 64 private Button mForceStopButton; 65 private DevicePolicyManager mDpm; 66 67 @Override onCreate(Bundle savedInstanceState)68 protected void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 setContentView(R.layout.application_details); 71 if (getIntent() != null && getIntent().getExtras() != null) { 72 mResolveInfo = getIntent().getExtras().getParcelable(APPLICATION_INFO_KEY); 73 } 74 if (mResolveInfo == null) { 75 Log.w(TAG, "No application info set."); 76 return; 77 } 78 79 mDisableToggle = (Button) findViewById(R.id.disable_toggle); 80 mForceStopButton = (Button) findViewById(R.id.force_stop); 81 82 mPermissionDetailView = (TextView) findViewById(R.id.permission_detail); 83 mPermissionContainer = findViewById(R.id.permission_container); 84 85 ImageView icon = (ImageView) findViewById(R.id.icon); 86 icon.setImageDrawable(mResolveInfo.loadIcon(getPackageManager())); 87 88 TextView appName = (TextView) findViewById(R.id.title); 89 appName.setText(mResolveInfo.loadLabel(getPackageManager())); 90 91 PermissionsSummaryHelper.getPermissionSummary(this /* context */, 92 mResolveInfo.activityInfo.packageName, mPermissionCallback); 93 mDpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); 94 updateForceStopButton(); 95 mForceStopButton.setOnClickListener(v -> { 96 forceStopPackage(mResolveInfo.activityInfo.packageName); 97 }); 98 updateDisableable(); 99 } 100 101 // fetch the latest ApplicationInfo instead of caching it so it reflects the current state. getAppInfo()102 private ApplicationInfo getAppInfo() { 103 try { 104 return getPackageManager().getApplicationInfo( 105 mResolveInfo.activityInfo.packageName, 0 /* flag */); 106 } catch (PackageManager.NameNotFoundException e) { 107 Log.e(TAG, "incorrect packagename: " + mResolveInfo.activityInfo.packageName, e); 108 throw new IllegalArgumentException(e); 109 } 110 } 111 getPackageInfo()112 private PackageInfo getPackageInfo() { 113 try { 114 return getPackageManager().getPackageInfo( 115 mResolveInfo.activityInfo.packageName, 0 /* flag */); 116 } catch (PackageManager.NameNotFoundException e) { 117 Log.e(TAG, "incorrect packagename: " + mResolveInfo.activityInfo.packageName, e); 118 throw new IllegalArgumentException(e); 119 } 120 } 121 updateDisableable()122 private void updateDisableable() { 123 boolean disableable = false; 124 boolean disabled = false; 125 // Try to prevent the user from bricking their phone 126 // by not allowing disabling of apps in the system. 127 if (Utils.isSystemPackage(getResources(), getPackageManager(), getPackageInfo())) { 128 // Disable button for core system applications. 129 mDisableToggle.setText(R.string.disable_text); 130 disabled = false; 131 } else if (getAppInfo().enabled && !isDisabledUntilUsed()) { 132 mDisableToggle.setText(R.string.disable_text); 133 disableable = true; 134 disabled = false; 135 } else { 136 mDisableToggle.setText(R.string.enable_text); 137 disableable = true; 138 disabled = true; 139 } 140 mDisableToggle.setEnabled(disableable); 141 final int enableState = disabled 142 ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 143 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 144 mDisableToggle.setOnClickListener(v -> { 145 getPackageManager().setApplicationEnabledSetting( 146 mResolveInfo.activityInfo.packageName, 147 enableState, 148 0); 149 updateDisableable(); 150 }); 151 } 152 isDisabledUntilUsed()153 private boolean isDisabledUntilUsed() { 154 return getAppInfo().enabledSetting 155 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 156 } 157 forceStopPackage(String pkgName)158 private void forceStopPackage(String pkgName) { 159 ActivityManager am = (ActivityManager) getSystemService( 160 Context.ACTIVITY_SERVICE); 161 Log.d(TAG, "Stopping package " + pkgName); 162 am.forceStopPackage(pkgName); 163 updateForceStopButton(); 164 } 165 166 // enable or disable the force stop button: 167 // - disabled if it's a device admin 168 // - if the application is stopped unexplicitly, enabled the button 169 // - if there's a reason for the system to restart the application, that indicates the app 170 // can be force stopped. updateForceStopButton()171 private void updateForceStopButton() { 172 if (mDpm.packageHasActiveAdmins(mResolveInfo.activityInfo.packageName)) { 173 // User can't force stop device admin. 174 if (Log.isLoggable(TAG, Log.DEBUG)) { 175 Log.d(TAG, "Disabling button, user can't force stop device admin"); 176 } 177 mForceStopButton.setEnabled(false); 178 } else if ((getAppInfo().flags & ApplicationInfo.FLAG_STOPPED) == 0) { 179 // If the app isn't explicitly stopped, then always show the 180 // force stop button. 181 if (Log.isLoggable(TAG, Log.WARN)) { 182 Log.w(TAG, "App is not explicitly stopped"); 183 } 184 mForceStopButton.setEnabled(true); 185 } else { 186 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 187 Uri.fromParts("package", mResolveInfo.activityInfo.packageName, null)); 188 intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{ 189 mResolveInfo.activityInfo.packageName 190 }); 191 192 if (Log.isLoggable(TAG, Log.DEBUG)) { 193 Log.d(TAG, "Sending broadcast to query restart for " 194 + mResolveInfo.activityInfo.packageName); 195 } 196 sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 197 mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); 198 } 199 } 200 201 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 202 @Override 203 public void onReceive(Context context, Intent intent) { 204 final boolean enabled = getResultCode() != Activity.RESULT_CANCELED; 205 if (Log.isLoggable(TAG, Log.DEBUG)) { 206 Log.d(TAG, 207 MessageFormat.format("Got broadcast response: Restart status for {0} {1}", 208 mResolveInfo.activityInfo.packageName, enabled)); 209 } 210 mForceStopButton.setEnabled(enabled); 211 } 212 }; 213 214 private final PermissionsResultCallback mPermissionCallback 215 = new PermissionsResultCallback() { 216 @Override 217 public void onPermissionSummaryResult(int standardGrantedPermissionCount, 218 int requestedPermissionCount, int additionalGrantedPermissionCount, 219 List<CharSequence> grantedGroupLabels) { 220 Resources res = getResources(); 221 CharSequence summary = null; 222 223 if (requestedPermissionCount == 0) { 224 summary = res.getString( 225 R.string.runtime_permissions_summary_no_permissions_requested); 226 mPermissionContainer.setEnabled(false); 227 mPermissionContainer.setOnClickListener(null); 228 } else { 229 ArrayList<CharSequence> list = new ArrayList<>(grantedGroupLabels); 230 if (additionalGrantedPermissionCount > 0) { 231 // N additional permissions. 232 list.add(res.getQuantityString( 233 R.plurals.runtime_permissions_additional_count, 234 additionalGrantedPermissionCount, additionalGrantedPermissionCount)); 235 } 236 if (list.size() == 0) { 237 summary = res.getString( 238 R.string.runtime_permissions_summary_no_permissions_granted); 239 } else { 240 summary = ListFormatter.getInstance().format(list); 241 } 242 mPermissionContainer.setEnabled(true); 243 mPermissionContainer.setOnClickListener(mPermissionClickedListener); 244 } 245 mPermissionDetailView.setText(summary); 246 } 247 }; 248 249 private OnClickListener mPermissionClickedListener = new OnClickListener() { 250 @Override 251 public void onClick(View v) { 252 // start new activity to manage app permissions 253 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); 254 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mResolveInfo.activityInfo.packageName); 255 try { 256 startActivity(intent); 257 } catch (ActivityNotFoundException e) { 258 Log.w(TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS"); 259 } 260 } 261 }; 262 } 263