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