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