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.text.BidiFormatter;
     20 import com.android.internal.util.MemInfoReader;
     21 import com.android.settings.R;
     22 
     23 import android.app.ActivityManager;
     24 import android.app.Dialog;
     25 import android.app.Fragment;
     26 import android.content.Context;
     27 import android.content.pm.PackageManager;
     28 import android.os.Bundle;
     29 import android.os.SystemClock;
     30 import android.os.UserHandle;
     31 import android.preference.PreferenceActivity;
     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 
     46 import java.util.ArrayList;
     47 import java.util.Collections;
     48 import java.util.HashMap;
     49 import java.util.Iterator;
     50 
     51 public class RunningProcessesView extends FrameLayout
     52         implements AdapterView.OnItemClickListener, RecyclerListener,
     53         RunningState.OnRefreshUiListener {
     54 
     55     final int mMyUserId;
     56 
     57     long SECONDARY_SERVER_MEM;
     58 
     59     final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
     60 
     61     ActivityManager mAm;
     62 
     63     RunningState mState;
     64 
     65     Fragment mOwner;
     66 
     67     Runnable mDataAvail;
     68 
     69     StringBuilder mBuilder = new StringBuilder(128);
     70 
     71     RunningState.BaseItem mCurSelected;
     72 
     73     ListView mListView;
     74     ServiceListAdapter mAdapter;
     75     LinearColorBar mColorBar;
     76     TextView mBackgroundProcessText;
     77     TextView mForegroundProcessText;
     78 
     79     int mLastNumBackgroundProcesses = -1;
     80     int mLastNumForegroundProcesses = -1;
     81     int mLastNumServiceProcesses = -1;
     82     long mLastBackgroundProcessMemory = -1;
     83     long mLastForegroundProcessMemory = -1;
     84     long mLastServiceProcessMemory = -1;
     85     long mLastAvailMemory = -1;
     86 
     87     Dialog mCurDialog;
     88 
     89     MemInfoReader mMemInfoReader = new MemInfoReader();
     90 
     91     public static class ActiveItem {
     92         View mRootView;
     93         RunningState.BaseItem mItem;
     94         ActivityManager.RunningServiceInfo mService;
     95         ViewHolder mHolder;
     96         long mFirstRunTime;
     97         boolean mSetBackground;
     98 
     99         void updateTime(Context context, StringBuilder builder) {
    100             TextView uptimeView = null;
    101 
    102             if (mItem instanceof RunningState.ServiceItem) {
    103                 // If we are displaying a service, then the service
    104                 // uptime goes at the top.
    105                 uptimeView = mHolder.size;
    106 
    107             } else {
    108                 String size = mItem.mSizeStr != null ? mItem.mSizeStr : "";
    109                 if (!size.equals(mItem.mCurSizeStr)) {
    110                     mItem.mCurSizeStr = size;
    111                     mHolder.size.setText(size);
    112                 }
    113 
    114                 if (mItem.mBackground) {
    115                     // This is a background process; no uptime.
    116                     if (!mSetBackground) {
    117                         mSetBackground = true;
    118                         mHolder.uptime.setText("");
    119                     }
    120                 } else if (mItem instanceof RunningState.MergedItem) {
    121                     // This item represents both services and processes,
    122                     // so show the service uptime below.
    123                     uptimeView = mHolder.uptime;
    124                 }
    125             }
    126 
    127             if (uptimeView != null) {
    128                 mSetBackground = false;
    129                 if (mFirstRunTime >= 0) {
    130                     //Log.i("foo", "Time for " + mItem.mDisplayLabel
    131                     //        + ": " + (SystemClock.uptimeMillis()-mFirstRunTime));
    132                     uptimeView.setText(DateUtils.formatElapsedTime(builder,
    133                             (SystemClock.elapsedRealtime()-mFirstRunTime)/1000));
    134                 } else {
    135                     boolean isService = false;
    136                     if (mItem instanceof RunningState.MergedItem) {
    137                         isService = ((RunningState.MergedItem)mItem).mServices.size() > 0;
    138                     }
    139                     if (isService) {
    140                         uptimeView.setText(context.getResources().getText(
    141                                 R.string.service_restarting));
    142                     } else {
    143                         uptimeView.setText("");
    144                     }
    145                 }
    146             }
    147         }
    148     }
    149 
    150     public static class ViewHolder {
    151         public View rootView;
    152         public ImageView icon;
    153         public TextView name;
    154         public TextView description;
    155         public TextView size;
    156         public TextView uptime;
    157 
    158         public ViewHolder(View v) {
    159             rootView = v;
    160             icon = (ImageView)v.findViewById(R.id.icon);
    161             name = (TextView)v.findViewById(R.id.name);
    162             description = (TextView)v.findViewById(R.id.description);
    163             size = (TextView)v.findViewById(R.id.size);
    164             uptime = (TextView)v.findViewById(R.id.uptime);
    165             v.setTag(this);
    166         }
    167 
    168         public ActiveItem bind(RunningState state, RunningState.BaseItem item,
    169                 StringBuilder builder) {
    170             synchronized (state.mLock) {
    171                 PackageManager pm = rootView.getContext().getPackageManager();
    172                 if (item.mPackageInfo == null && item instanceof RunningState.MergedItem) {
    173                     // Items for background processes don't normally load
    174                     // their labels for performance reasons.  Do it now.
    175                     RunningState.MergedItem mergedItem = (RunningState.MergedItem)item;
    176                     if (mergedItem.mProcess != null) {
    177                         ((RunningState.MergedItem)item).mProcess.ensureLabel(pm);
    178                         item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo;
    179                         item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel;
    180                     }
    181                 }
    182                 name.setText(item.mDisplayLabel);
    183                 ActiveItem ai = new ActiveItem();
    184                 ai.mRootView = rootView;
    185                 ai.mItem = item;
    186                 ai.mHolder = this;
    187                 ai.mFirstRunTime = item.mActiveSince;
    188                 if (item.mBackground) {
    189                     description.setText(rootView.getContext().getText(R.string.cached));
    190                 } else {
    191                     description.setText(item.mDescription);
    192                 }
    193                 item.mCurSizeStr = null;
    194                 icon.setImageDrawable(item.loadIcon(rootView.getContext(), state));
    195                 icon.setVisibility(View.VISIBLE);
    196                 ai.updateTime(rootView.getContext(), builder);
    197                 return ai;
    198             }
    199         }
    200     }
    201 
    202     static class TimeTicker extends TextView {
    203         public TimeTicker(Context context, AttributeSet attrs) {
    204             super(context, attrs);
    205         }
    206     }
    207 
    208     class ServiceListAdapter extends BaseAdapter {
    209         final RunningState mState;
    210         final LayoutInflater mInflater;
    211         boolean mShowBackground;
    212         ArrayList<RunningState.MergedItem> mOrigItems;
    213         final ArrayList<RunningState.MergedItem> mItems
    214                 = new ArrayList<RunningState.MergedItem>();
    215 
    216         ServiceListAdapter(RunningState state) {
    217             mState = state;
    218             mInflater = (LayoutInflater)getContext().getSystemService(
    219                     Context.LAYOUT_INFLATER_SERVICE);
    220             refreshItems();
    221         }
    222 
    223         void setShowBackground(boolean showBackground) {
    224             if (mShowBackground != showBackground) {
    225                 mShowBackground = showBackground;
    226                 mState.setWatchingBackgroundItems(showBackground);
    227                 refreshItems();
    228                 notifyDataSetChanged();
    229                 mColorBar.setShowingGreen(mShowBackground);
    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 = (ServiceListAdapter)(mListView.getAdapter());
    320             adapter.refreshItems();
    321             adapter.notifyDataSetChanged();
    322         }
    323 
    324         if (mDataAvail != null) {
    325             mDataAvail.run();
    326             mDataAvail = null;
    327         }
    328 
    329         // This is the amount of available memory until we start killing
    330         // background services.
    331         mMemInfoReader.readMemInfo();
    332         long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
    333                 - SECONDARY_SERVER_MEM;
    334         if (availMem < 0) {
    335             availMem = 0;
    336         }
    337 
    338         synchronized (mState.mLock) {
    339             if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses
    340                     || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory
    341                     || mLastAvailMemory != availMem) {
    342                 mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses;
    343                 mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory;
    344                 mLastAvailMemory = availMem;
    345                 long freeMem = mLastAvailMemory + mLastBackgroundProcessMemory;
    346                 BidiFormatter bidiFormatter = BidiFormatter.getInstance();
    347                 String sizeStr = bidiFormatter.unicodeWrap(
    348                         Formatter.formatShortFileSize(getContext(), freeMem));
    349                 mBackgroundProcessText.setText(getResources().getString(
    350                         R.string.service_background_processes, sizeStr));
    351                 sizeStr = bidiFormatter.unicodeWrap(
    352                         Formatter.formatShortFileSize(getContext(),
    353                                 mMemInfoReader.getTotalSize() - freeMem));
    354                 mForegroundProcessText.setText(getResources().getString(
    355                         R.string.service_foreground_processes, sizeStr));
    356             }
    357             if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses
    358                     || mLastForegroundProcessMemory != mState.mForegroundProcessMemory
    359                     || mLastNumServiceProcesses != mState.mNumServiceProcesses
    360                     || mLastServiceProcessMemory != mState.mServiceProcessMemory) {
    361                 mLastNumForegroundProcesses = mState.mNumForegroundProcesses;
    362                 mLastForegroundProcessMemory = mState.mForegroundProcessMemory;
    363                 mLastNumServiceProcesses = mState.mNumServiceProcesses;
    364                 mLastServiceProcessMemory = mState.mServiceProcessMemory;
    365                 /*
    366                 String sizeStr = Formatter.formatShortFileSize(getContext(),
    367                         mLastForegroundProcessMemory + mLastServiceProcessMemory);
    368                 mForegroundProcessText.setText(getResources().getString(
    369                         R.string.service_foreground_processes, sizeStr));
    370                 */
    371             }
    372 
    373             float totalMem = mMemInfoReader.getTotalSize();
    374             float totalShownMem = availMem + mLastBackgroundProcessMemory
    375                     + mLastServiceProcessMemory;
    376             mColorBar.setRatios((totalMem-totalShownMem)/totalMem,
    377                     mLastServiceProcessMemory/totalMem,
    378                     mLastBackgroundProcessMemory/totalMem);
    379         }
    380     }
    381 
    382     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    383         ListView l = (ListView)parent;
    384         RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position);
    385         mCurSelected = mi;
    386         startServiceDetailsActivity(mi);
    387     }
    388 
    389     // utility method used to start sub activity
    390     private void startServiceDetailsActivity(RunningState.MergedItem mi) {
    391         if (mOwner != null) {
    392             // start new fragment to display extended information
    393             Bundle args = new Bundle();
    394             if (mi.mProcess != null) {
    395                 args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
    396                 args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
    397             }
    398             args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
    399             args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
    400 
    401             PreferenceActivity pa = (PreferenceActivity)mOwner.getActivity();
    402             pa.startPreferencePanel(RunningServiceDetails.class.getName(), args,
    403                     R.string.runningservicedetails_settings_title, null, null, 0);
    404         }
    405     }
    406 
    407     public void onMovedToScrapHeap(View view) {
    408         mActiveItems.remove(view);
    409     }
    410 
    411     public RunningProcessesView(Context context, AttributeSet attrs) {
    412         super(context, attrs);
    413         mMyUserId = UserHandle.myUserId();
    414     }
    415 
    416     public void doCreate(Bundle savedInstanceState) {
    417         mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
    418         mState = RunningState.getInstance(getContext());
    419         LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
    420                 Context.LAYOUT_INFLATER_SERVICE);
    421         inflater.inflate(R.layout.running_processes_view, this);
    422         mListView = (ListView)findViewById(android.R.id.list);
    423         View emptyView = findViewById(com.android.internal.R.id.empty);
    424         if (emptyView != null) {
    425             mListView.setEmptyView(emptyView);
    426         }
    427         mListView.setOnItemClickListener(this);
    428         mListView.setRecyclerListener(this);
    429         mAdapter = new ServiceListAdapter(mState);
    430         mListView.setAdapter(mAdapter);
    431         mColorBar = (LinearColorBar)findViewById(R.id.color_bar);
    432         mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText);
    433         mBackgroundProcessText.setOnClickListener(new View.OnClickListener() {
    434             @Override
    435             public void onClick(View v) {
    436                 mAdapter.setShowBackground(true);
    437             }
    438         });
    439         mForegroundProcessText = (TextView)findViewById(R.id.foregroundText);
    440         mForegroundProcessText.setOnClickListener(new View.OnClickListener() {
    441             @Override
    442             public void onClick(View v) {
    443                 mAdapter.setShowBackground(false);
    444             }
    445         });
    446 
    447         ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
    448         mAm.getMemoryInfo(memInfo);
    449         SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
    450     }
    451 
    452     public void doPause() {
    453         mState.pause();
    454         mDataAvail = null;
    455         mOwner = null;
    456     }
    457 
    458     public boolean doResume(Fragment owner, Runnable dataAvail) {
    459         mOwner = owner;
    460         mState.resume(this);
    461         if (mState.hasData()) {
    462             // If the state already has its data, then let's populate our
    463             // list right now to avoid flicker.
    464             refreshUi(true);
    465             return true;
    466         }
    467         mDataAvail = dataAvail;
    468         return false;
    469     }
    470 
    471     void updateTimes() {
    472         Iterator<ActiveItem> it = mActiveItems.values().iterator();
    473         while (it.hasNext()) {
    474             ActiveItem ai = it.next();
    475             if (ai.mRootView.getWindowToken() == null) {
    476                 // Clean out any dead views, just in case.
    477                 it.remove();
    478                 continue;
    479             }
    480             ai.updateTime(getContext(), mBuilder);
    481         }
    482     }
    483 
    484     @Override
    485     public void onRefreshUi(int what) {
    486         switch (what) {
    487             case REFRESH_TIME:
    488                 updateTimes();
    489                 break;
    490             case REFRESH_DATA:
    491                 refreshUi(false);
    492                 updateTimes();
    493                 break;
    494             case REFRESH_STRUCTURE:
    495                 refreshUi(true);
    496                 updateTimes();
    497                 break;
    498         }
    499     }
    500 }
    501