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