Home | History | Annotate | Download | only in applications
      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
    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.equals(Utils.OS_PKG) ? null
    125                         : AppInfoWithHeader.getInfoIntent(this, mApp.mPackage));
    126     }
    127 
    128     @Override
    129     protected int getMetricsCategory() {
    130         return MetricsLogger.APPLICATIONS_PROCESS_STATS_DETAIL;
    131     }
    132 
    133     @Override
    134     public void onResume() {
    135         super.onResume();
    136 
    137         checkForceStop();
    138         updateRunningServices();
    139     }
    140 
    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 
    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
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    410     private void doStopService(String pkg, String name) {
    411         getActivity().stopService(new Intent().setClassName(pkg, name));
    412         updateRunningServices();
    413     }
    414 
    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 
    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