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