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