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