RunningProcessesView.java revision 37f3c8f574b984e4980ebf598358726b3f852156
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 android.text.BidiFormatter;
20import com.android.internal.util.MemInfoReader;
21import com.android.settings.R;
22
23import android.app.ActivityManager;
24import android.app.Dialog;
25import android.app.Fragment;
26import android.content.Context;
27import android.content.pm.PackageManager;
28import android.os.Bundle;
29import android.os.SystemClock;
30import android.os.UserHandle;
31import android.preference.PreferenceActivity;
32import android.text.format.DateUtils;
33import android.text.format.Formatter;
34import android.util.AttributeSet;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.AdapterView;
39import android.widget.BaseAdapter;
40import android.widget.FrameLayout;
41import android.widget.ImageView;
42import android.widget.ListView;
43import android.widget.TextView;
44import android.widget.AbsListView.RecyclerListener;
45
46import java.util.ArrayList;
47import java.util.Collections;
48import java.util.HashMap;
49import java.util.Iterator;
50
51public 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