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.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
     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
    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
    128     protected int getMetricsCategory() {
    129         return MetricsEvent.APPLICATIONS_PROCESS_STATS_DETAIL;
    130     }
    131 
    132     @Override
    133     public void onResume() {
    134         super.onResume();
    135 
    136         checkForceStop();
    137         updateRunningServices();
    138     }
    139 
    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 
    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
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    408     private void doStopService(String pkg, String name) {
    409         getActivity().stopService(new Intent().setClassName(pkg, name));
    410         updateRunningServices();
    411     }
    412 
    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 
    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