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