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