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