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.content.Context;
22import android.content.pm.PackageManager;
23import android.content.res.ColorStateList;
24import android.graphics.PorterDuff;
25import android.os.Bundle;
26import android.os.SystemClock;
27import android.os.UserHandle;
28import android.text.BidiFormatter;
29import android.text.format.DateUtils;
30import android.text.format.Formatter;
31import android.util.AttributeSet;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import android.widget.AbsListView.RecyclerListener;
36import android.widget.AdapterView;
37import android.widget.BaseAdapter;
38import android.widget.FrameLayout;
39import android.widget.ImageView;
40import android.widget.ListView;
41import android.widget.ProgressBar;
42import android.widget.TextView;
43
44import com.android.internal.util.MemInfoReader;
45import com.android.settings.R;
46import com.android.settings.SettingsPreferenceFragment;
47import com.android.settings.Utils;
48import com.android.settings.core.SubSettingLauncher;
49
50import java.util.ArrayList;
51import java.util.Collections;
52import java.util.HashMap;
53import java.util.Iterator;
54
55public class RunningProcessesView extends FrameLayout
56        implements AdapterView.OnItemClickListener, RecyclerListener,
57        RunningState.OnRefreshUiListener {
58
59    final int mMyUserId;
60
61    long SECONDARY_SERVER_MEM;
62
63    final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
64
65    ActivityManager mAm;
66
67    RunningState mState;
68
69    SettingsPreferenceFragment mOwner;
70
71    Runnable mDataAvail;
72
73    StringBuilder mBuilder = new StringBuilder(128);
74
75    RunningState.BaseItem mCurSelected;
76
77    ListView mListView;
78    View mHeader;
79    ServiceListAdapter mAdapter;
80    ProgressBar mColorBar;
81    TextView mBackgroundProcessPrefix;
82    TextView mAppsProcessPrefix;
83    TextView mForegroundProcessPrefix;
84    TextView mBackgroundProcessText;
85    TextView mAppsProcessText;
86    TextView mForegroundProcessText;
87
88    long mCurTotalRam = -1;
89    long mCurHighRam = -1;      // "System" or "Used"
90    long mCurMedRam = -1;       // "Apps" or "Cached"
91    long mCurLowRam = -1;       // "Free"
92    boolean mCurShowCached = false;
93
94    Dialog mCurDialog;
95
96    MemInfoReader mMemInfoReader = new MemInfoReader();
97
98    public static class ActiveItem {
99        View mRootView;
100        RunningState.BaseItem mItem;
101        ActivityManager.RunningServiceInfo mService;
102        ViewHolder mHolder;
103        long mFirstRunTime;
104        boolean mSetBackground;
105
106        void updateTime(Context context, StringBuilder builder) {
107            TextView uptimeView = null;
108
109            if (mItem instanceof RunningState.ServiceItem) {
110                // If we are displaying a service, then the service
111                // uptime goes at the top.
112                uptimeView = mHolder.size;
113
114            } else {
115                String size = mItem.mSizeStr != null ? mItem.mSizeStr : "";
116                if (!size.equals(mItem.mCurSizeStr)) {
117                    mItem.mCurSizeStr = size;
118                    mHolder.size.setText(size);
119                }
120
121                if (mItem.mBackground) {
122                    // This is a background process; no uptime.
123                    if (!mSetBackground) {
124                        mSetBackground = true;
125                        mHolder.uptime.setText("");
126                    }
127                } else if (mItem instanceof RunningState.MergedItem) {
128                    // This item represents both services and processes,
129                    // so show the service uptime below.
130                    uptimeView = mHolder.uptime;
131                }
132            }
133
134            if (uptimeView != null) {
135                mSetBackground = false;
136                if (mFirstRunTime >= 0) {
137                    //Log.i("foo", "Time for " + mItem.mDisplayLabel
138                    //        + ": " + (SystemClock.uptimeMillis()-mFirstRunTime));
139                    uptimeView.setText(DateUtils.formatElapsedTime(builder,
140                            (SystemClock.elapsedRealtime()-mFirstRunTime)/1000));
141                } else {
142                    boolean isService = false;
143                    if (mItem instanceof RunningState.MergedItem) {
144                        isService = ((RunningState.MergedItem)mItem).mServices.size() > 0;
145                    }
146                    if (isService) {
147                        uptimeView.setText(context.getResources().getText(
148                                R.string.service_restarting));
149                    } else {
150                        uptimeView.setText("");
151                    }
152                }
153            }
154        }
155    }
156
157    public static class ViewHolder {
158        public View rootView;
159        public ImageView icon;
160        public TextView name;
161        public TextView description;
162        public TextView size;
163        public TextView uptime;
164
165        public ViewHolder(View v) {
166            rootView = v;
167            icon = (ImageView)v.findViewById(R.id.icon);
168            name = (TextView)v.findViewById(R.id.name);
169            description = (TextView)v.findViewById(R.id.description);
170            size = (TextView)v.findViewById(R.id.size);
171            uptime = (TextView)v.findViewById(R.id.uptime);
172            v.setTag(this);
173        }
174
175        public ActiveItem bind(RunningState state, RunningState.BaseItem item,
176                StringBuilder builder) {
177            synchronized (state.mLock) {
178                PackageManager pm = rootView.getContext().getPackageManager();
179                if (item.mPackageInfo == null && item instanceof RunningState.MergedItem) {
180                    // Items for background processes don't normally load
181                    // their labels for performance reasons.  Do it now.
182                    RunningState.MergedItem mergedItem = (RunningState.MergedItem)item;
183                    if (mergedItem.mProcess != null) {
184                        ((RunningState.MergedItem)item).mProcess.ensureLabel(pm);
185                        item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo;
186                        item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel;
187                    }
188                }
189                name.setText(item.mDisplayLabel);
190                ActiveItem ai = new ActiveItem();
191                ai.mRootView = rootView;
192                ai.mItem = item;
193                ai.mHolder = this;
194                ai.mFirstRunTime = item.mActiveSince;
195                if (item.mBackground) {
196                    description.setText(rootView.getContext().getText(R.string.cached));
197                } else {
198                    description.setText(item.mDescription);
199                }
200                item.mCurSizeStr = null;
201                icon.setImageDrawable(item.loadIcon(rootView.getContext(), state));
202                icon.setVisibility(View.VISIBLE);
203                ai.updateTime(rootView.getContext(), builder);
204                return ai;
205            }
206        }
207    }
208
209    class ServiceListAdapter extends BaseAdapter {
210        final RunningState mState;
211        final LayoutInflater mInflater;
212        boolean mShowBackground;
213        ArrayList<RunningState.MergedItem> mOrigItems;
214        final ArrayList<RunningState.MergedItem> mItems
215                = new ArrayList<RunningState.MergedItem>();
216
217        ServiceListAdapter(RunningState state) {
218            mState = state;
219            mInflater = (LayoutInflater)getContext().getSystemService(
220                    Context.LAYOUT_INFLATER_SERVICE);
221            refreshItems();
222        }
223
224        void setShowBackground(boolean showBackground) {
225            if (mShowBackground != showBackground) {
226                mShowBackground = showBackground;
227                mState.setWatchingBackgroundItems(showBackground);
228                refreshItems();
229                refreshUi(true);
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 = mAdapter;
320            adapter.refreshItems();
321            adapter.notifyDataSetChanged();
322        }
323
324        if (mDataAvail != null) {
325            mDataAvail.run();
326            mDataAvail = null;
327        }
328
329        mMemInfoReader.readMemInfo();
330
331        /*
332        // This is the amount of available memory until we start killing
333        // background services.
334        long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
335                - SECONDARY_SERVER_MEM;
336        if (availMem < 0) {
337            availMem = 0;
338        }
339        */
340
341        synchronized (mState.mLock) {
342            if (mCurShowCached != mAdapter.mShowBackground) {
343                mCurShowCached = mAdapter.mShowBackground;
344                if (mCurShowCached) {
345                    mForegroundProcessPrefix.setText(getResources().getText(
346                            R.string.running_processes_header_used_prefix));
347                    mAppsProcessPrefix.setText(getResources().getText(
348                            R.string.running_processes_header_cached_prefix));
349                } else {
350                    mForegroundProcessPrefix.setText(getResources().getText(
351                            R.string.running_processes_header_system_prefix));
352                    mAppsProcessPrefix.setText(getResources().getText(
353                            R.string.running_processes_header_apps_prefix));
354                }
355            }
356
357            final long totalRam = mMemInfoReader.getTotalSize();
358            final long medRam;
359            final long lowRam;
360            if (mCurShowCached) {
361                lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize();
362                medRam = mState.mBackgroundProcessMemory;
363            } else {
364                lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
365                        + mState.mBackgroundProcessMemory;
366                medRam = mState.mServiceProcessMemory;
367
368            }
369            final long highRam = totalRam - medRam - lowRam;
370
371            if (mCurTotalRam != totalRam || mCurHighRam != highRam || mCurMedRam != medRam
372                    || mCurLowRam != lowRam) {
373                mCurTotalRam = totalRam;
374                mCurHighRam = highRam;
375                mCurMedRam = medRam;
376                mCurLowRam = lowRam;
377                BidiFormatter bidiFormatter = BidiFormatter.getInstance();
378                String sizeStr = bidiFormatter.unicodeWrap(
379                        Formatter.formatShortFileSize(getContext(), lowRam));
380                mBackgroundProcessText.setText(getResources().getString(
381                        R.string.running_processes_header_ram, sizeStr));
382                sizeStr = bidiFormatter.unicodeWrap(
383                        Formatter.formatShortFileSize(getContext(), medRam));
384                mAppsProcessText.setText(getResources().getString(
385                        R.string.running_processes_header_ram, sizeStr));
386                sizeStr = bidiFormatter.unicodeWrap(
387                        Formatter.formatShortFileSize(getContext(), highRam));
388                mForegroundProcessText.setText(getResources().getString(
389                        R.string.running_processes_header_ram, sizeStr));
390                int progress = (int) ((highRam/(float) totalRam) * 100);
391                mColorBar.setProgress(progress);
392                mColorBar.setSecondaryProgress(progress + (int) ((medRam/(float) totalRam) * 100));
393            }
394        }
395    }
396
397    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
398        ListView l = (ListView)parent;
399        RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position);
400        mCurSelected = mi;
401        startServiceDetailsActivity(mi);
402    }
403
404    // utility method used to start sub activity
405    private void startServiceDetailsActivity(RunningState.MergedItem mi) {
406        if (mOwner != null && mi != null) {
407            // start new fragment to display extended information
408            Bundle args = new Bundle();
409            if (mi.mProcess != null) {
410                args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
411                args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
412            }
413            args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
414            args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
415
416            new SubSettingLauncher(getContext())
417                    .setDestination(RunningServiceDetails.class.getName())
418                    .setArguments(args)
419                    .setTitle(R.string.runningservicedetails_settings_title)
420                    .setSourceMetricsCategory(mOwner.getMetricsCategory())
421                    .launch();
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 = mHeader.findViewById(R.id.color_bar);
452        final Context context = getContext();
453        mColorBar.setProgressTintList(
454                ColorStateList.valueOf(context.getColor(R.color.running_processes_system_ram)));
455        mColorBar.setSecondaryProgressTintList(
456                ColorStateList.valueOf(Utils.getColorAccent(context)));
457        mColorBar.setSecondaryProgressTintMode(PorterDuff.Mode.SRC);
458        mColorBar.setProgressBackgroundTintList(
459                ColorStateList.valueOf(context.getColor(R.color.running_processes_free_ram)));
460        mColorBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC);
461        mBackgroundProcessPrefix = mHeader.findViewById(R.id.freeSizePrefix);
462        mAppsProcessPrefix = mHeader.findViewById(R.id.appsSizePrefix);
463        mForegroundProcessPrefix = mHeader.findViewById(R.id.systemSizePrefix);
464        mBackgroundProcessText = mHeader.findViewById(R.id.freeSize);
465        mAppsProcessText = mHeader.findViewById(R.id.appsSize);
466        mForegroundProcessText = mHeader.findViewById(R.id.systemSize);
467
468        ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
469        mAm.getMemoryInfo(memInfo);
470        SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
471    }
472
473    public void doPause() {
474        mState.pause();
475        mDataAvail = null;
476        mOwner = null;
477    }
478
479    public boolean doResume(SettingsPreferenceFragment owner, Runnable dataAvail) {
480        mOwner = owner;
481        mState.resume(this);
482        if (mState.hasData()) {
483            // If the state already has its data, then let's populate our
484            // list right now to avoid flicker.
485            refreshUi(true);
486            return true;
487        }
488        mDataAvail = dataAvail;
489        return false;
490    }
491
492    void updateTimes() {
493        Iterator<ActiveItem> it = mActiveItems.values().iterator();
494        while (it.hasNext()) {
495            ActiveItem ai = it.next();
496            if (ai.mRootView.getWindowToken() == null) {
497                // Clean out any dead views, just in case.
498                it.remove();
499                continue;
500            }
501            ai.updateTime(getContext(), mBuilder);
502        }
503    }
504
505    @Override
506    public void onRefreshUi(int what) {
507        switch (what) {
508            case REFRESH_TIME:
509                updateTimes();
510                break;
511            case REFRESH_DATA:
512                refreshUi(false);
513                updateTimes();
514                break;
515            case REFRESH_STRUCTURE:
516                refreshUi(true);
517                updateTimes();
518                break;
519        }
520    }
521}
522