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