Home | History | Annotate | Download | only in applications
      1 /*
      2  * Copyright (C) 2010 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.Dialog;
     21 import android.content.Context;
     22 import android.content.pm.PackageManager;
     23 import android.content.res.ColorStateList;
     24 import android.graphics.PorterDuff;
     25 import android.os.Bundle;
     26 import android.os.SystemClock;
     27 import android.os.UserHandle;
     28 import android.text.BidiFormatter;
     29 import android.text.format.DateUtils;
     30 import android.text.format.Formatter;
     31 import android.util.AttributeSet;
     32 import android.view.LayoutInflater;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.widget.AbsListView.RecyclerListener;
     36 import android.widget.AdapterView;
     37 import android.widget.BaseAdapter;
     38 import android.widget.FrameLayout;
     39 import android.widget.ImageView;
     40 import android.widget.ListView;
     41 import android.widget.ProgressBar;
     42 import android.widget.TextView;
     43 
     44 import com.android.internal.util.MemInfoReader;
     45 import com.android.settings.R;
     46 import com.android.settings.SettingsPreferenceFragment;
     47 import com.android.settings.Utils;
     48 import com.android.settings.core.SubSettingLauncher;
     49 
     50 import java.util.ArrayList;
     51 import java.util.Collections;
     52 import java.util.HashMap;
     53 import java.util.Iterator;
     54 
     55 public class RunningProcessesView extends FrameLayout
     56         implements AdapterView.OnItemClickListener, RecyclerListener,
     57         RunningState.OnRefreshUiListener {
     58 
     59     final int mMyUserId;
     60 
     61     long SECONDARY_SERVER_MEM;
     62 
     63     final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
     64 
     65     ActivityManager mAm;
     66 
     67     RunningState mState;
     68 
     69     SettingsPreferenceFragment mOwner;
     70 
     71     Runnable mDataAvail;
     72 
     73     StringBuilder mBuilder = new StringBuilder(128);
     74 
     75     RunningState.BaseItem mCurSelected;
     76 
     77     ListView mListView;
     78     View mHeader;
     79     ServiceListAdapter mAdapter;
     80     ProgressBar mColorBar;
     81     TextView mBackgroundProcessPrefix;
     82     TextView mAppsProcessPrefix;
     83     TextView mForegroundProcessPrefix;
     84     TextView mBackgroundProcessText;
     85     TextView mAppsProcessText;
     86     TextView mForegroundProcessText;
     87 
     88     long mCurTotalRam = -1;
     89     long mCurHighRam = -1;      // "System" or "Used"
     90     long mCurMedRam = -1;       // "Apps" or "Cached"
     91     long mCurLowRam = -1;       // "Free"
     92     boolean mCurShowCached = false;
     93 
     94     Dialog mCurDialog;
     95 
     96     MemInfoReader mMemInfoReader = new MemInfoReader();
     97 
     98     public static class ActiveItem {
     99         View mRootView;
    100         RunningState.BaseItem mItem;
    101         ActivityManager.RunningServiceInfo mService;
    102         ViewHolder mHolder;
    103         long mFirstRunTime;
    104         boolean mSetBackground;
    105 
    106         void updateTime(Context context, StringBuilder builder) {
    107             TextView uptimeView = null;
    108 
    109             if (mItem instanceof RunningState.ServiceItem) {
    110                 // If we are displaying a service, then the service
    111                 // uptime goes at the top.
    112                 uptimeView = mHolder.size;
    113 
    114             } else {
    115                 String size = mItem.mSizeStr != null ? mItem.mSizeStr : "";
    116                 if (!size.equals(mItem.mCurSizeStr)) {
    117                     mItem.mCurSizeStr = size;
    118                     mHolder.size.setText(size);
    119                 }
    120 
    121                 if (mItem.mBackground) {
    122                     // This is a background process; no uptime.
    123                     if (!mSetBackground) {
    124                         mSetBackground = true;
    125                         mHolder.uptime.setText("");
    126                     }
    127                 } else if (mItem instanceof RunningState.MergedItem) {
    128                     // This item represents both services and processes,
    129                     // so show the service uptime below.
    130                     uptimeView = mHolder.uptime;
    131                 }
    132             }
    133 
    134             if (uptimeView != null) {
    135                 mSetBackground = false;
    136                 if (mFirstRunTime >= 0) {
    137                     //Log.i("foo", "Time for " + mItem.mDisplayLabel
    138                     //        + ": " + (SystemClock.uptimeMillis()-mFirstRunTime));
    139                     uptimeView.setText(DateUtils.formatElapsedTime(builder,
    140                             (SystemClock.elapsedRealtime()-mFirstRunTime)/1000));
    141                 } else {
    142                     boolean isService = false;
    143                     if (mItem instanceof RunningState.MergedItem) {
    144                         isService = ((RunningState.MergedItem)mItem).mServices.size() > 0;
    145                     }
    146                     if (isService) {
    147                         uptimeView.setText(context.getResources().getText(
    148                                 R.string.service_restarting));
    149                     } else {
    150                         uptimeView.setText("");
    151                     }
    152                 }
    153             }
    154         }
    155     }
    156 
    157     public static class ViewHolder {
    158         public View rootView;
    159         public ImageView icon;
    160         public TextView name;
    161         public TextView description;
    162         public TextView size;
    163         public TextView uptime;
    164 
    165         public ViewHolder(View v) {
    166             rootView = v;
    167             icon = (ImageView)v.findViewById(R.id.icon);
    168             name = (TextView)v.findViewById(R.id.name);
    169             description = (TextView)v.findViewById(R.id.description);
    170             size = (TextView)v.findViewById(R.id.size);
    171             uptime = (TextView)v.findViewById(R.id.uptime);
    172             v.setTag(this);
    173         }
    174 
    175         public ActiveItem bind(RunningState state, RunningState.BaseItem item,
    176                 StringBuilder builder) {
    177             synchronized (state.mLock) {
    178                 PackageManager pm = rootView.getContext().getPackageManager();
    179                 if (item.mPackageInfo == null && item instanceof RunningState.MergedItem) {
    180                     // Items for background processes don't normally load
    181                     // their labels for performance reasons.  Do it now.
    182                     RunningState.MergedItem mergedItem = (RunningState.MergedItem)item;
    183                     if (mergedItem.mProcess != null) {
    184                         ((RunningState.MergedItem)item).mProcess.ensureLabel(pm);
    185                         item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo;
    186                         item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel;
    187                     }
    188                 }
    189                 name.setText(item.mDisplayLabel);
    190                 ActiveItem ai = new ActiveItem();
    191                 ai.mRootView = rootView;
    192                 ai.mItem = item;
    193                 ai.mHolder = this;
    194                 ai.mFirstRunTime = item.mActiveSince;
    195                 if (item.mBackground) {
    196                     description.setText(rootView.getContext().getText(R.string.cached));
    197                 } else {
    198                     description.setText(item.mDescription);
    199                 }
    200                 item.mCurSizeStr = null;
    201                 icon.setImageDrawable(item.loadIcon(rootView.getContext(), state));
    202                 icon.setVisibility(View.VISIBLE);
    203                 ai.updateTime(rootView.getContext(), builder);
    204                 return ai;
    205             }
    206         }
    207     }
    208 
    209     class ServiceListAdapter extends BaseAdapter {
    210         final RunningState mState;
    211         final LayoutInflater mInflater;
    212         boolean mShowBackground;
    213         ArrayList<RunningState.MergedItem> mOrigItems;
    214         final ArrayList<RunningState.MergedItem> mItems
    215                 = new ArrayList<RunningState.MergedItem>();
    216 
    217         ServiceListAdapter(RunningState state) {
    218             mState = state;
    219             mInflater = (LayoutInflater)getContext().getSystemService(
    220                     Context.LAYOUT_INFLATER_SERVICE);
    221             refreshItems();
    222         }
    223 
    224         void setShowBackground(boolean showBackground) {
    225             if (mShowBackground != showBackground) {
    226                 mShowBackground = showBackground;
    227                 mState.setWatchingBackgroundItems(showBackground);
    228                 refreshItems();
    229                 refreshUi(true);
    230             }
    231         }
    232 
    233         boolean getShowBackground() {
    234             return mShowBackground;
    235         }
    236 
    237         void refreshItems() {
    238             ArrayList<RunningState.MergedItem> newItems =
    239                 mShowBackground ? mState.getCurrentBackgroundItems()
    240                         : mState.getCurrentMergedItems();
    241             if (mOrigItems != newItems) {
    242                 mOrigItems = newItems;
    243                 if (newItems == null) {
    244                     mItems.clear();
    245                 } else {
    246                     mItems.clear();
    247                     mItems.addAll(newItems);
    248                     if (mShowBackground) {
    249                         Collections.sort(mItems, mState.mBackgroundComparator);
    250                     }
    251                 }
    252             }
    253         }
    254 
    255         public boolean hasStableIds() {
    256             return true;
    257         }
    258 
    259         public int getCount() {
    260             return mItems.size();
    261         }
    262 
    263         @Override
    264         public boolean isEmpty() {
    265             return mState.hasData() && mItems.size() == 0;
    266         }
    267 
    268         public Object getItem(int position) {
    269             return mItems.get(position);
    270         }
    271 
    272         public long getItemId(int position) {
    273             return mItems.get(position).hashCode();
    274         }
    275 
    276         public boolean areAllItemsEnabled() {
    277             return false;
    278         }
    279 
    280         public boolean isEnabled(int position) {
    281             return !mItems.get(position).mIsProcess;
    282         }
    283 
    284         public View getView(int position, View convertView, ViewGroup parent) {
    285             View v;
    286             if (convertView == null) {
    287                 v = newView(parent);
    288             } else {
    289                 v = convertView;
    290             }
    291             bindView(v, position);
    292             return v;
    293         }
    294 
    295         public View newView(ViewGroup parent) {
    296             View v = mInflater.inflate(R.layout.running_processes_item, parent, false);
    297             new ViewHolder(v);
    298             return v;
    299         }
    300 
    301         public void bindView(View view, int position) {
    302             synchronized (mState.mLock) {
    303                 if (position >= mItems.size()) {
    304                     // List must have changed since we last reported its
    305                     // size...  ignore here, we will be doing a data changed
    306                     // to refresh the entire list.
    307                     return;
    308                 }
    309                 ViewHolder vh = (ViewHolder) view.getTag();
    310                 RunningState.MergedItem item = mItems.get(position);
    311                 ActiveItem ai = vh.bind(mState, item, mBuilder);
    312                 mActiveItems.put(view, ai);
    313             }
    314         }
    315     }
    316 
    317     void refreshUi(boolean dataChanged) {
    318         if (dataChanged) {
    319             ServiceListAdapter adapter = mAdapter;
    320             adapter.refreshItems();
    321             adapter.notifyDataSetChanged();
    322         }
    323 
    324         if (mDataAvail != null) {
    325             mDataAvail.run();
    326             mDataAvail = null;
    327         }
    328 
    329         mMemInfoReader.readMemInfo();
    330 
    331         /*
    332         // This is the amount of available memory until we start killing
    333         // background services.
    334         long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
    335                 - SECONDARY_SERVER_MEM;
    336         if (availMem < 0) {
    337             availMem = 0;
    338         }
    339         */
    340 
    341         synchronized (mState.mLock) {
    342             if (mCurShowCached != mAdapter.mShowBackground) {
    343                 mCurShowCached = mAdapter.mShowBackground;
    344                 if (mCurShowCached) {
    345                     mForegroundProcessPrefix.setText(getResources().getText(
    346                             R.string.running_processes_header_used_prefix));
    347                     mAppsProcessPrefix.setText(getResources().getText(
    348                             R.string.running_processes_header_cached_prefix));
    349                 } else {
    350                     mForegroundProcessPrefix.setText(getResources().getText(
    351                             R.string.running_processes_header_system_prefix));
    352                     mAppsProcessPrefix.setText(getResources().getText(
    353                             R.string.running_processes_header_apps_prefix));
    354                 }
    355             }
    356 
    357             final long totalRam = mMemInfoReader.getTotalSize();
    358             final long medRam;
    359             final long lowRam;
    360             if (mCurShowCached) {
    361                 lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize();
    362                 medRam = mState.mBackgroundProcessMemory;
    363             } else {
    364                 lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
    365                         + mState.mBackgroundProcessMemory;
    366                 medRam = mState.mServiceProcessMemory;
    367 
    368             }
    369             final long highRam = totalRam - medRam - lowRam;
    370 
    371             if (mCurTotalRam != totalRam || mCurHighRam != highRam || mCurMedRam != medRam
    372                     || mCurLowRam != lowRam) {
    373                 mCurTotalRam = totalRam;
    374                 mCurHighRam = highRam;
    375                 mCurMedRam = medRam;
    376                 mCurLowRam = lowRam;
    377                 BidiFormatter bidiFormatter = BidiFormatter.getInstance();
    378                 String sizeStr = bidiFormatter.unicodeWrap(
    379                         Formatter.formatShortFileSize(getContext(), lowRam));
    380                 mBackgroundProcessText.setText(getResources().getString(
    381                         R.string.running_processes_header_ram, sizeStr));
    382                 sizeStr = bidiFormatter.unicodeWrap(
    383                         Formatter.formatShortFileSize(getContext(), medRam));
    384                 mAppsProcessText.setText(getResources().getString(
    385                         R.string.running_processes_header_ram, sizeStr));
    386                 sizeStr = bidiFormatter.unicodeWrap(
    387                         Formatter.formatShortFileSize(getContext(), highRam));
    388                 mForegroundProcessText.setText(getResources().getString(
    389                         R.string.running_processes_header_ram, sizeStr));
    390                 int progress = (int) ((highRam/(float) totalRam) * 100);
    391                 mColorBar.setProgress(progress);
    392                 mColorBar.setSecondaryProgress(progress + (int) ((medRam/(float) totalRam) * 100));
    393             }
    394         }
    395     }
    396 
    397     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    398         ListView l = (ListView)parent;
    399         RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position);
    400         mCurSelected = mi;
    401         startServiceDetailsActivity(mi);
    402     }
    403 
    404     // utility method used to start sub activity
    405     private void startServiceDetailsActivity(RunningState.MergedItem mi) {
    406         if (mOwner != null && mi != null) {
    407             // start new fragment to display extended information
    408             Bundle args = new Bundle();
    409             if (mi.mProcess != null) {
    410                 args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
    411                 args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
    412             }
    413             args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
    414             args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
    415 
    416             new SubSettingLauncher(getContext())
    417                     .setDestination(RunningServiceDetails.class.getName())
    418                     .setArguments(args)
    419                     .setTitle(R.string.runningservicedetails_settings_title)
    420                     .setSourceMetricsCategory(mOwner.getMetricsCategory())
    421                     .launch();
    422         }
    423     }
    424 
    425     public void onMovedToScrapHeap(View view) {
    426         mActiveItems.remove(view);
    427     }
    428 
    429     public RunningProcessesView(Context context, AttributeSet attrs) {
    430         super(context, attrs);
    431         mMyUserId = UserHandle.myUserId();
    432     }
    433 
    434     public void doCreate() {
    435         mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
    436         mState = RunningState.getInstance(getContext());
    437         LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
    438                 Context.LAYOUT_INFLATER_SERVICE);
    439         inflater.inflate(R.layout.running_processes_view, this);
    440         mListView = (ListView)findViewById(android.R.id.list);
    441         View emptyView = findViewById(com.android.internal.R.id.empty);
    442         if (emptyView != null) {
    443             mListView.setEmptyView(emptyView);
    444         }
    445         mListView.setOnItemClickListener(this);
    446         mListView.setRecyclerListener(this);
    447         mAdapter = new ServiceListAdapter(mState);
    448         mListView.setAdapter(mAdapter);
    449         mHeader = inflater.inflate(R.layout.running_processes_header, null);
    450         mListView.addHeaderView(mHeader, null, false /* set as not selectable */);
    451         mColorBar = mHeader.findViewById(R.id.color_bar);
    452         final Context context = getContext();
    453         mColorBar.setProgressTintList(
    454                 ColorStateList.valueOf(context.getColor(R.color.running_processes_system_ram)));
    455         mColorBar.setSecondaryProgressTintList(
    456                 ColorStateList.valueOf(Utils.getColorAccent(context)));
    457         mColorBar.setSecondaryProgressTintMode(PorterDuff.Mode.SRC);
    458         mColorBar.setProgressBackgroundTintList(
    459                 ColorStateList.valueOf(context.getColor(R.color.running_processes_free_ram)));
    460         mColorBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC);
    461         mBackgroundProcessPrefix = mHeader.findViewById(R.id.freeSizePrefix);
    462         mAppsProcessPrefix = mHeader.findViewById(R.id.appsSizePrefix);
    463         mForegroundProcessPrefix = mHeader.findViewById(R.id.systemSizePrefix);
    464         mBackgroundProcessText = mHeader.findViewById(R.id.freeSize);
    465         mAppsProcessText = mHeader.findViewById(R.id.appsSize);
    466         mForegroundProcessText = mHeader.findViewById(R.id.systemSize);
    467 
    468         ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
    469         mAm.getMemoryInfo(memInfo);
    470         SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
    471     }
    472 
    473     public void doPause() {
    474         mState.pause();
    475         mDataAvail = null;
    476         mOwner = null;
    477     }
    478 
    479     public boolean doResume(SettingsPreferenceFragment owner, Runnable dataAvail) {
    480         mOwner = owner;
    481         mState.resume(this);
    482         if (mState.hasData()) {
    483             // If the state already has its data, then let's populate our
    484             // list right now to avoid flicker.
    485             refreshUi(true);
    486             return true;
    487         }
    488         mDataAvail = dataAvail;
    489         return false;
    490     }
    491 
    492     void updateTimes() {
    493         Iterator<ActiveItem> it = mActiveItems.values().iterator();
    494         while (it.hasNext()) {
    495             ActiveItem ai = it.next();
    496             if (ai.mRootView.getWindowToken() == null) {
    497                 // Clean out any dead views, just in case.
    498                 it.remove();
    499                 continue;
    500             }
    501             ai.updateTime(getContext(), mBuilder);
    502         }
    503     }
    504 
    505     @Override
    506     public void onRefreshUi(int what) {
    507         switch (what) {
    508             case REFRESH_TIME:
    509                 updateTimes();
    510                 break;
    511             case REFRESH_DATA:
    512                 refreshUi(false);
    513                 updateTimes();
    514                 break;
    515             case REFRESH_STRUCTURE:
    516                 refreshUi(true);
    517                 updateTimes();
    518                 break;
    519         }
    520     }
    521 }
    522