1 /* 2 * Copyright (C) 2013 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 17 package com.android.settings.applications; 18 19 import static com.android.settings.widget.EntityHeaderController.ActionType; 20 21 import android.app.Activity; 22 import android.app.ActivityManager; 23 import android.app.ActivityManager.RunningServiceInfo; 24 import android.app.admin.DevicePolicyManager; 25 import android.app.settings.SettingsEnums; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.pm.ApplicationInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.content.pm.ServiceInfo; 34 import android.graphics.drawable.ColorDrawable; 35 import android.os.Bundle; 36 import android.os.Process; 37 import android.os.UserHandle; 38 import android.text.format.Formatter; 39 import android.util.ArrayMap; 40 import android.util.IconDrawableFactory; 41 import android.util.Log; 42 import android.view.Menu; 43 import android.view.MenuInflater; 44 import android.view.MenuItem; 45 import android.view.View; 46 47 import androidx.appcompat.app.AlertDialog; 48 import androidx.preference.Preference; 49 import androidx.preference.PreferenceCategory; 50 51 import com.android.settings.CancellablePreference; 52 import com.android.settings.CancellablePreference.OnCancelListener; 53 import com.android.settings.R; 54 import com.android.settings.SettingsPreferenceFragment; 55 import com.android.settings.SummaryPreference; 56 import com.android.settings.applications.ProcStatsEntry.Service; 57 import com.android.settings.widget.EntityHeaderController; 58 59 import java.util.ArrayList; 60 import java.util.Collections; 61 import java.util.Comparator; 62 import java.util.HashMap; 63 import java.util.List; 64 65 public class ProcessStatsDetail extends SettingsPreferenceFragment { 66 67 private static final String TAG = "ProcessStatsDetail"; 68 69 public static final int MENU_FORCE_STOP = 1; 70 71 public static final String EXTRA_PACKAGE_ENTRY = "package_entry"; 72 public static final String EXTRA_WEIGHT_TO_RAM = "weight_to_ram"; 73 public static final String EXTRA_TOTAL_TIME = "total_time"; 74 public static final String EXTRA_MAX_MEMORY_USAGE = "max_memory_usage"; 75 public static final String EXTRA_TOTAL_SCALE = "total_scale"; 76 77 private static final String KEY_DETAILS_HEADER = "status_header"; 78 79 private static final String KEY_FREQUENCY = "frequency"; 80 private static final String KEY_MAX_USAGE = "max_usage"; 81 82 private static final String KEY_PROCS = "processes"; 83 84 private final ArrayMap<ComponentName, CancellablePreference> mServiceMap = new ArrayMap<>(); 85 86 private PackageManager mPm; 87 private DevicePolicyManager mDpm; 88 89 private MenuItem mForceStop; 90 91 private ProcStatsPackageEntry mApp; 92 private double mWeightToRam; 93 private long mTotalTime; 94 private long mOnePercentTime; 95 96 private double mMaxMemoryUsage; 97 98 private double mTotalScale; 99 100 private PreferenceCategory mProcGroup; 101 102 @Override onCreate(Bundle icicle)103 public void onCreate(Bundle icicle) { 104 super.onCreate(icicle); 105 mPm = getActivity().getPackageManager(); 106 mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); 107 final Bundle args = getArguments(); 108 mApp = args.getParcelable(EXTRA_PACKAGE_ENTRY); 109 mApp.retrieveUiData(getActivity(), mPm); 110 mWeightToRam = args.getDouble(EXTRA_WEIGHT_TO_RAM); 111 mTotalTime = args.getLong(EXTRA_TOTAL_TIME); 112 mMaxMemoryUsage = args.getDouble(EXTRA_MAX_MEMORY_USAGE); 113 mTotalScale = args.getDouble(EXTRA_TOTAL_SCALE); 114 mOnePercentTime = mTotalTime/100; 115 116 mServiceMap.clear(); 117 createDetails(); 118 setHasOptionsMenu(true); 119 } 120 121 @Override onViewCreated(View view, Bundle savedInstanceState)122 public void onViewCreated(View view, Bundle savedInstanceState) { 123 super.onViewCreated(view, savedInstanceState); 124 125 if (mApp.mUiTargetApp == null) { 126 finish(); 127 return; 128 } 129 final Activity activity = getActivity(); 130 final Preference pref = EntityHeaderController 131 .newInstance(activity, this, null /* appHeader */) 132 .setRecyclerView(getListView(), getSettingsLifecycle()) 133 .setIcon(mApp.mUiTargetApp != null 134 ? IconDrawableFactory.newInstance(activity).getBadgedIcon(mApp.mUiTargetApp) 135 : new ColorDrawable(0)) 136 .setLabel(mApp.mUiLabel) 137 .setPackageName(mApp.mPackage) 138 .setUid(mApp.mUiTargetApp != null 139 ? mApp.mUiTargetApp.uid 140 : UserHandle.USER_NULL) 141 .setHasAppInfoLink(true) 142 .setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE) 143 .done(activity, getPrefContext()); 144 getPreferenceScreen().addPreference(pref); 145 } 146 147 @Override getMetricsCategory()148 public int getMetricsCategory() { 149 return SettingsEnums.APPLICATIONS_PROCESS_STATS_DETAIL; 150 } 151 152 @Override onResume()153 public void onResume() { 154 super.onResume(); 155 156 checkForceStop(); 157 updateRunningServices(); 158 } 159 updateRunningServices()160 private void updateRunningServices() { 161 ActivityManager activityManager = (ActivityManager) 162 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 163 List<RunningServiceInfo> runningServices = 164 activityManager.getRunningServices(Integer.MAX_VALUE); 165 166 // Set all services as not running, then turn back on the ones we find. 167 int N = mServiceMap.size(); 168 for (int i = 0; i < N; i++) { 169 mServiceMap.valueAt(i).setCancellable(false); 170 } 171 172 N = runningServices.size(); 173 for (int i = 0; i < N; i++) { 174 RunningServiceInfo runningService = runningServices.get(i); 175 if (!runningService.started && runningService.clientLabel == 0) { 176 continue; 177 } 178 if ((runningService.flags & RunningServiceInfo.FLAG_PERSISTENT_PROCESS) != 0) { 179 continue; 180 } 181 final ComponentName service = runningService.service; 182 CancellablePreference pref = mServiceMap.get(service); 183 if (pref != null) { 184 pref.setOnCancelListener(new OnCancelListener() { 185 @Override 186 public void onCancel(CancellablePreference preference) { 187 stopService(service.getPackageName(), service.getClassName()); 188 } 189 }); 190 pref.setCancellable(true); 191 } 192 } 193 } 194 createDetails()195 private void createDetails() { 196 addPreferencesFromResource(R.xml.app_memory_settings); 197 198 mProcGroup = (PreferenceCategory) findPreference(KEY_PROCS); 199 fillProcessesSection(); 200 201 SummaryPreference summaryPreference = (SummaryPreference) findPreference(KEY_DETAILS_HEADER); 202 203 // TODO: Find way to share this code with ProcessStatsPreference. 204 boolean statsForeground = mApp.mRunWeight > mApp.mBgWeight; 205 double avgRam = (statsForeground ? mApp.mRunWeight : mApp.mBgWeight) * mWeightToRam; 206 float avgRatio = (float) (avgRam / mMaxMemoryUsage); 207 float remainingRatio = 1 - avgRatio; 208 Context context = getActivity(); 209 summaryPreference.setRatios(avgRatio, 0, remainingRatio); 210 Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), 211 (long) avgRam, Formatter.FLAG_SHORTER); 212 summaryPreference.setAmount(usedResult.value); 213 summaryPreference.setUnits(usedResult.units); 214 215 long duration = Math.max(mApp.mRunDuration, mApp.mBgDuration); 216 CharSequence frequency = ProcStatsPackageEntry.getFrequency(duration 217 / (float) mTotalTime, getActivity()); 218 findPreference(KEY_FREQUENCY).setSummary(frequency); 219 double max = Math.max(mApp.mMaxBgMem, mApp.mMaxRunMem) * mTotalScale * 1024; 220 findPreference(KEY_MAX_USAGE).setSummary( 221 Formatter.formatShortFileSize(getContext(), (long) max)); 222 } 223 224 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)225 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 226 mForceStop = menu.add(0, MENU_FORCE_STOP, 0, R.string.force_stop); 227 checkForceStop(); 228 } 229 230 @Override onOptionsItemSelected(MenuItem item)231 public boolean onOptionsItemSelected(MenuItem item) { 232 switch (item.getItemId()) { 233 case MENU_FORCE_STOP: 234 killProcesses(); 235 return true; 236 } 237 return false; 238 } 239 240 final static Comparator<ProcStatsEntry> sEntryCompare = new Comparator<ProcStatsEntry>() { 241 @Override 242 public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) { 243 if (lhs.mRunWeight < rhs.mRunWeight) { 244 return 1; 245 } else if (lhs.mRunWeight > rhs.mRunWeight) { 246 return -1; 247 } 248 return 0; 249 } 250 }; 251 fillProcessesSection()252 private void fillProcessesSection() { 253 mProcGroup.removeAll(); 254 final ArrayList<ProcStatsEntry> entries = new ArrayList<>(); 255 for (int ie = 0; ie < mApp.mEntries.size(); ie++) { 256 ProcStatsEntry entry = mApp.mEntries.get(ie); 257 if (entry.mPackage.equals("os")) { 258 entry.mLabel = entry.mName; 259 } else { 260 entry.mLabel = getProcessName(mApp.mUiLabel, entry); 261 } 262 entries.add(entry); 263 } 264 Collections.sort(entries, sEntryCompare); 265 for (int ie = 0; ie < entries.size(); ie++) { 266 ProcStatsEntry entry = entries.get(ie); 267 Preference processPref = new Preference(getPrefContext()); 268 processPref.setTitle(entry.mLabel); 269 processPref.setSelectable(false); 270 271 long duration = Math.max(entry.mRunDuration, entry.mBgDuration); 272 long memoryUse = Math.max((long) (entry.mRunWeight * mWeightToRam), 273 (long) (entry.mBgWeight * mWeightToRam)); 274 String memoryString = Formatter.formatShortFileSize(getActivity(), memoryUse); 275 CharSequence frequency = ProcStatsPackageEntry.getFrequency(duration 276 / (float) mTotalTime, getActivity()); 277 processPref.setSummary( 278 getString(R.string.memory_use_running_format, memoryString, frequency)); 279 mProcGroup.addPreference(processPref); 280 } 281 if (mProcGroup.getPreferenceCount() < 2) { 282 getPreferenceScreen().removePreference(mProcGroup); 283 } 284 } 285 capitalize(String processName)286 private static String capitalize(String processName) { 287 char c = processName.charAt(0); 288 if (!Character.isLowerCase(c)) { 289 return processName; 290 } 291 return Character.toUpperCase(c) + processName.substring(1); 292 } 293 getProcessName(String appLabel, ProcStatsEntry entry)294 private static String getProcessName(String appLabel, ProcStatsEntry entry) { 295 String processName = entry.mName; 296 if (processName.contains(":")) { 297 return capitalize(processName.substring(processName.lastIndexOf(':') + 1)); 298 } 299 if (processName.startsWith(entry.mPackage)) { 300 if (processName.length() == entry.mPackage.length()) { 301 return appLabel; 302 } 303 int start = entry.mPackage.length(); 304 if (processName.charAt(start) == '.') { 305 start++; 306 } 307 return capitalize(processName.substring(start)); 308 } 309 return processName; 310 } 311 312 final static Comparator<ProcStatsEntry.Service> sServiceCompare 313 = new Comparator<ProcStatsEntry.Service>() { 314 @Override 315 public int compare(ProcStatsEntry.Service lhs, ProcStatsEntry.Service rhs) { 316 if (lhs.mDuration < rhs.mDuration) { 317 return 1; 318 } else if (lhs.mDuration > rhs.mDuration) { 319 return -1; 320 } 321 return 0; 322 } 323 }; 324 325 final static Comparator<PkgService> sServicePkgCompare = new Comparator<PkgService>() { 326 @Override 327 public int compare(PkgService lhs, PkgService rhs) { 328 if (lhs.mDuration < rhs.mDuration) { 329 return 1; 330 } else if (lhs.mDuration > rhs.mDuration) { 331 return -1; 332 } 333 return 0; 334 } 335 }; 336 337 static class PkgService { 338 final ArrayList<ProcStatsEntry.Service> mServices = new ArrayList<>(); 339 long mDuration; 340 } 341 fillServicesSection(ProcStatsEntry entry, PreferenceCategory processPref)342 private void fillServicesSection(ProcStatsEntry entry, PreferenceCategory processPref) { 343 final HashMap<String, PkgService> pkgServices = new HashMap<>(); 344 final ArrayList<PkgService> pkgList = new ArrayList<>(); 345 for (int ip = 0; ip < entry.mServices.size(); ip++) { 346 String pkg = entry.mServices.keyAt(ip); 347 PkgService psvc = null; 348 ArrayList<ProcStatsEntry.Service> services = entry.mServices.valueAt(ip); 349 for (int is=services.size()-1; is>=0; is--) { 350 ProcStatsEntry.Service pent = services.get(is); 351 if (pent.mDuration >= mOnePercentTime) { 352 if (psvc == null) { 353 psvc = pkgServices.get(pkg); 354 if (psvc == null) { 355 psvc = new PkgService(); 356 pkgServices.put(pkg, psvc); 357 pkgList.add(psvc); 358 } 359 } 360 psvc.mServices.add(pent); 361 psvc.mDuration += pent.mDuration; 362 } 363 } 364 } 365 Collections.sort(pkgList, sServicePkgCompare); 366 for (int ip = 0; ip < pkgList.size(); ip++) { 367 ArrayList<ProcStatsEntry.Service> services = pkgList.get(ip).mServices; 368 Collections.sort(services, sServiceCompare); 369 for (int is=0; is<services.size(); is++) { 370 final ProcStatsEntry.Service service = services.get(is); 371 CharSequence label = getLabel(service); 372 CancellablePreference servicePref = new CancellablePreference(getPrefContext()); 373 servicePref.setSelectable(false); 374 servicePref.setTitle(label); 375 servicePref.setSummary(ProcStatsPackageEntry.getFrequency( 376 service.mDuration / (float) mTotalTime, getActivity())); 377 processPref.addPreference(servicePref); 378 mServiceMap.put(new ComponentName(service.mPackage, service.mName), servicePref); 379 } 380 } 381 } 382 getLabel(Service service)383 private CharSequence getLabel(Service service) { 384 // Try to get the service label, on the off chance that one exists. 385 try { 386 ServiceInfo serviceInfo = getPackageManager().getServiceInfo( 387 new ComponentName(service.mPackage, service.mName), 0); 388 if (serviceInfo.labelRes != 0) { 389 return serviceInfo.loadLabel(getPackageManager()); 390 } 391 } catch (NameNotFoundException e) { 392 } 393 String label = service.mName; 394 int tail = label.lastIndexOf('.'); 395 if (tail >= 0 && tail < (label.length()-1)) { 396 label = label.substring(tail+1); 397 } 398 return label; 399 } 400 stopService(String pkg, String name)401 private void stopService(String pkg, String name) { 402 try { 403 ApplicationInfo appInfo = getActivity().getPackageManager().getApplicationInfo(pkg, 0); 404 if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 405 showStopServiceDialog(pkg, name); 406 return; 407 } 408 } catch (NameNotFoundException e) { 409 Log.e(TAG, "Can't find app " + pkg, e); 410 return; 411 } 412 doStopService(pkg, name); 413 } 414 showStopServiceDialog(final String pkg, final String name)415 private void showStopServiceDialog(final String pkg, final String name) { 416 new AlertDialog.Builder(getActivity()) 417 .setTitle(R.string.runningservicedetails_stop_dlg_title) 418 .setMessage(R.string.runningservicedetails_stop_dlg_text) 419 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 420 public void onClick(DialogInterface dialog, int which) { 421 doStopService(pkg, name); 422 } 423 }) 424 .setNegativeButton(R.string.dlg_cancel, null) 425 .show(); 426 } 427 doStopService(String pkg, String name)428 private void doStopService(String pkg, String name) { 429 getActivity().stopService(new Intent().setClassName(pkg, name)); 430 updateRunningServices(); 431 } 432 killProcesses()433 private void killProcesses() { 434 ActivityManager am = (ActivityManager)getActivity().getSystemService( 435 Context.ACTIVITY_SERVICE); 436 for (int i=0; i< mApp.mEntries.size(); i++) { 437 ProcStatsEntry ent = mApp.mEntries.get(i); 438 for (int j=0; j<ent.mPackages.size(); j++) { 439 am.forceStopPackage(ent.mPackages.get(j)); 440 } 441 } 442 } 443 checkForceStop()444 private void checkForceStop() { 445 if (mForceStop == null) { 446 return; 447 } 448 if (mApp.mEntries.get(0).mUid < Process.FIRST_APPLICATION_UID) { 449 mForceStop.setVisible(false); 450 return; 451 } 452 boolean isStarted = false; 453 for (int i=0; i< mApp.mEntries.size(); i++) { 454 ProcStatsEntry ent = mApp.mEntries.get(i); 455 for (int j=0; j<ent.mPackages.size(); j++) { 456 String pkg = ent.mPackages.get(j); 457 if (mDpm.packageHasActiveAdmins(pkg)) { 458 mForceStop.setEnabled(false); 459 return; 460 } 461 try { 462 ApplicationInfo info = mPm.getApplicationInfo(pkg, 0); 463 if ((info.flags&ApplicationInfo.FLAG_STOPPED) == 0) { 464 isStarted = true; 465 } 466 } catch (PackageManager.NameNotFoundException e) { 467 } 468 } 469 } 470 if (isStarted) { 471 mForceStop.setVisible(true); 472 } 473 } 474 } 475