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