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.app.ActivityManager;
20import android.app.Dialog;
21import android.app.Fragment;
22import android.content.Context;
23import android.content.pm.PackageManager;
24import android.os.Bundle;
25import android.os.SystemClock;
26import android.os.UserHandle;
27import android.text.BidiFormatter;
28import android.text.format.DateUtils;
29import android.text.format.Formatter;
30import android.util.AttributeSet;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.widget.AbsListView.RecyclerListener;
35import android.widget.AdapterView;
36import android.widget.BaseAdapter;
37import android.widget.FrameLayout;
38import android.widget.ImageView;
39import android.widget.ListView;
40import android.widget.TextView;
41
42import com.android.internal.util.MemInfoReader;
43import com.android.settings.R;
44import com.android.settings.SettingsActivity;
45import com.android.settings.Utils;
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    class ServiceListAdapter extends BaseAdapter {
207        final RunningState mState;
208        final LayoutInflater mInflater;
209        boolean mShowBackground;
210        ArrayList<RunningState.MergedItem> mOrigItems;
211        final ArrayList<RunningState.MergedItem> mItems
212                = new ArrayList<RunningState.MergedItem>();
213
214        ServiceListAdapter(RunningState state) {
215            mState = state;
216            mInflater = (LayoutInflater)getContext().getSystemService(
217                    Context.LAYOUT_INFLATER_SERVICE);
218            refreshItems();
219        }
220
221        void setShowBackground(boolean showBackground) {
222            if (mShowBackground != showBackground) {
223                mShowBackground = showBackground;
224                mState.setWatchingBackgroundItems(showBackground);
225                refreshItems();
226                refreshUi(true);
227            }
228        }
229
230        boolean getShowBackground() {
231            return mShowBackground;
232        }
233
234        void refreshItems() {
235            ArrayList<RunningState.MergedItem> newItems =
236                mShowBackground ? mState.getCurrentBackgroundItems()
237                        : mState.getCurrentMergedItems();
238            if (mOrigItems != newItems) {
239                mOrigItems = newItems;
240                if (newItems == null) {
241                    mItems.clear();
242                } else {
243                    mItems.clear();
244                    mItems.addAll(newItems);
245                    if (mShowBackground) {
246                        Collections.sort(mItems, mState.mBackgroundComparator);
247                    }
248                }
249            }
250        }
251
252        public boolean hasStableIds() {
253            return true;
254        }
255
256        public int getCount() {
257            return mItems.size();
258        }
259
260        @Override
261        public boolean isEmpty() {
262            return mState.hasData() && mItems.size() == 0;
263        }
264
265        public Object getItem(int position) {
266            return mItems.get(position);
267        }
268
269        public long getItemId(int position) {
270            return mItems.get(position).hashCode();
271        }
272
273        public boolean areAllItemsEnabled() {
274            return false;
275        }
276
277        public boolean isEnabled(int position) {
278            return !mItems.get(position).mIsProcess;
279        }
280
281        public View getView(int position, View convertView, ViewGroup parent) {
282            View v;
283            if (convertView == null) {
284                v = newView(parent);
285            } else {
286                v = convertView;
287            }
288            bindView(v, position);
289            return v;
290        }
291
292        public View newView(ViewGroup parent) {
293            View v = mInflater.inflate(R.layout.running_processes_item, parent, false);
294            new ViewHolder(v);
295            return v;
296        }
297
298        public void bindView(View view, int position) {
299            synchronized (mState.mLock) {
300                if (position >= mItems.size()) {
301                    // List must have changed since we last reported its
302                    // size...  ignore here, we will be doing a data changed
303                    // to refresh the entire list.
304                    return;
305                }
306                ViewHolder vh = (ViewHolder) view.getTag();
307                RunningState.MergedItem item = mItems.get(position);
308                ActiveItem ai = vh.bind(mState, item, mBuilder);
309                mActiveItems.put(view, ai);
310            }
311        }
312    }
313
314    void refreshUi(boolean dataChanged) {
315        if (dataChanged) {
316            ServiceListAdapter adapter = mAdapter;
317            adapter.refreshItems();
318            adapter.notifyDataSetChanged();
319        }
320
321        if (mDataAvail != null) {
322            mDataAvail.run();
323            mDataAvail = null;
324        }
325
326        mMemInfoReader.readMemInfo();
327
328        /*
329        // This is the amount of available memory until we start killing
330        // background services.
331        long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
332                - SECONDARY_SERVER_MEM;
333        if (availMem < 0) {
334            availMem = 0;
335        }
336        */
337
338        synchronized (mState.mLock) {
339            if (mCurShowCached != mAdapter.mShowBackground) {
340                mCurShowCached = mAdapter.mShowBackground;
341                if (mCurShowCached) {
342                    mForegroundProcessPrefix.setText(getResources().getText(
343                            R.string.running_processes_header_used_prefix));
344                    mAppsProcessPrefix.setText(getResources().getText(
345                            R.string.running_processes_header_cached_prefix));
346                } else {
347                    mForegroundProcessPrefix.setText(getResources().getText(
348                            R.string.running_processes_header_system_prefix));
349                    mAppsProcessPrefix.setText(getResources().getText(
350                            R.string.running_processes_header_apps_prefix));
351                }
352            }
353
354            final long totalRam = mMemInfoReader.getTotalSize();
355            final long medRam;
356            final long lowRam;
357            if (mCurShowCached) {
358                lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize();
359                medRam = mState.mBackgroundProcessMemory;
360            } else {
361                lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
362                        + mState.mBackgroundProcessMemory;
363                medRam = mState.mServiceProcessMemory;
364
365            }
366            final long highRam = totalRam - medRam - lowRam;
367
368            if (mCurTotalRam != totalRam || mCurHighRam != highRam || mCurMedRam != medRam
369                    || mCurLowRam != lowRam) {
370                mCurTotalRam = totalRam;
371                mCurHighRam = highRam;
372                mCurMedRam = medRam;
373                mCurLowRam = lowRam;
374                BidiFormatter bidiFormatter = BidiFormatter.getInstance();
375                String sizeStr = bidiFormatter.unicodeWrap(
376                        Formatter.formatShortFileSize(getContext(), lowRam));
377                mBackgroundProcessText.setText(getResources().getString(
378                        R.string.running_processes_header_ram, sizeStr));
379                sizeStr = bidiFormatter.unicodeWrap(
380                        Formatter.formatShortFileSize(getContext(), medRam));
381                mAppsProcessText.setText(getResources().getString(
382                        R.string.running_processes_header_ram, sizeStr));
383                sizeStr = bidiFormatter.unicodeWrap(
384                        Formatter.formatShortFileSize(getContext(), highRam));
385                mForegroundProcessText.setText(getResources().getString(
386                        R.string.running_processes_header_ram, sizeStr));
387                mColorBar.setRatios(highRam/(float)totalRam,
388                        medRam/(float)totalRam,
389                        lowRam/(float)totalRam);
390            }
391        }
392    }
393
394    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
395        ListView l = (ListView)parent;
396        RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position);
397        mCurSelected = mi;
398        startServiceDetailsActivity(mi);
399    }
400
401    // utility method used to start sub activity
402    private void startServiceDetailsActivity(RunningState.MergedItem mi) {
403        if (mOwner != null && mi != null) {
404            // start new fragment to display extended information
405            Bundle args = new Bundle();
406            if (mi.mProcess != null) {
407                args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
408                args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
409            }
410            args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
411            args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
412
413            SettingsActivity sa = (SettingsActivity) mOwner.getActivity();
414            sa.startPreferencePanel(mOwner, RunningServiceDetails.class.getName(), args,
415                    R.string.runningservicedetails_settings_title, null, null, 0);
416        }
417    }
418
419    public void onMovedToScrapHeap(View view) {
420        mActiveItems.remove(view);
421    }
422
423    public RunningProcessesView(Context context, AttributeSet attrs) {
424        super(context, attrs);
425        mMyUserId = UserHandle.myUserId();
426    }
427
428    public void doCreate() {
429        mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
430        mState = RunningState.getInstance(getContext());
431        LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
432                Context.LAYOUT_INFLATER_SERVICE);
433        inflater.inflate(R.layout.running_processes_view, this);
434        mListView = (ListView)findViewById(android.R.id.list);
435        View emptyView = findViewById(com.android.internal.R.id.empty);
436        if (emptyView != null) {
437            mListView.setEmptyView(emptyView);
438        }
439        mListView.setOnItemClickListener(this);
440        mListView.setRecyclerListener(this);
441        mAdapter = new ServiceListAdapter(mState);
442        mListView.setAdapter(mAdapter);
443        mHeader = inflater.inflate(R.layout.running_processes_header, null);
444        mListView.addHeaderView(mHeader, null, false /* set as not selectable */);
445        mColorBar = (LinearColorBar)mHeader.findViewById(R.id.color_bar);
446        final Context context = getContext();
447        mColorBar.setColors(context.getColor(R.color.running_processes_system_ram),
448                Utils.getColorAccent(context),
449                context.getColor(R.color.running_processes_free_ram));
450        mBackgroundProcessPrefix = (TextView)mHeader.findViewById(R.id.freeSizePrefix);
451        mAppsProcessPrefix = (TextView)mHeader.findViewById(R.id.appsSizePrefix);
452        mForegroundProcessPrefix = (TextView)mHeader.findViewById(R.id.systemSizePrefix);
453        mBackgroundProcessText = (TextView)mHeader.findViewById(R.id.freeSize);
454        mAppsProcessText = (TextView)mHeader.findViewById(R.id.appsSize);
455        mForegroundProcessText = (TextView)mHeader.findViewById(R.id.systemSize);
456
457        ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
458        mAm.getMemoryInfo(memInfo);
459        SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
460    }
461
462    public void doPause() {
463        mState.pause();
464        mDataAvail = null;
465        mOwner = null;
466    }
467
468    public boolean doResume(Fragment owner, Runnable dataAvail) {
469        mOwner = owner;
470        mState.resume(this);
471        if (mState.hasData()) {
472            // If the state already has its data, then let's populate our
473            // list right now to avoid flicker.
474            refreshUi(true);
475            return true;
476        }
477        mDataAvail = dataAvail;
478        return false;
479    }
480
481    void updateTimes() {
482        Iterator<ActiveItem> it = mActiveItems.values().iterator();
483        while (it.hasNext()) {
484            ActiveItem ai = it.next();
485            if (ai.mRootView.getWindowToken() == null) {
486                // Clean out any dead views, just in case.
487                it.remove();
488                continue;
489            }
490            ai.updateTime(getContext(), mBuilder);
491        }
492    }
493
494    @Override
495    public void onRefreshUi(int what) {
496        switch (what) {
497            case REFRESH_TIME:
498                updateTimes();
499                break;
500            case REFRESH_DATA:
501                refreshUi(false);
502                updateTimes();
503                break;
504            case REFRESH_STRUCTURE:
505                refreshUi(true);
506                updateTimes();
507                break;
508        }
509    }
510}
511