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