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