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 
17 package com.android.settings.applications;
18 
19 import android.app.ActivityManager;
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.content.res.ColorStateList;
24 import android.graphics.PorterDuff;
25 import android.os.Bundle;
26 import android.os.SystemClock;
27 import android.os.UserHandle;
28 import android.text.BidiFormatter;
29 import android.text.format.DateUtils;
30 import android.text.format.Formatter;
31 import android.util.AttributeSet;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.AbsListView.RecyclerListener;
36 import android.widget.AdapterView;
37 import android.widget.BaseAdapter;
38 import android.widget.FrameLayout;
39 import android.widget.ImageView;
40 import android.widget.ListView;
41 import android.widget.ProgressBar;
42 import android.widget.TextView;
43 
44 import com.android.internal.util.MemInfoReader;
45 import com.android.settings.R;
46 import com.android.settings.SettingsPreferenceFragment;
47 import com.android.settings.Utils;
48 import com.android.settings.core.SubSettingLauncher;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.Iterator;
54 
55 public 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 
updateTime(Context context, StringBuilder builder)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 
ViewHolder(View v)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 
bind(RunningState state, RunningState.BaseItem item, StringBuilder builder)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 
ServiceListAdapter(RunningState state)217         ServiceListAdapter(RunningState state) {
218             mState = state;
219             mInflater = (LayoutInflater)getContext().getSystemService(
220                     Context.LAYOUT_INFLATER_SERVICE);
221             refreshItems();
222         }
223 
setShowBackground(boolean showBackground)224         void setShowBackground(boolean showBackground) {
225             if (mShowBackground != showBackground) {
226                 mShowBackground = showBackground;
227                 mState.setWatchingBackgroundItems(showBackground);
228                 refreshItems();
229                 refreshUi(true);
230             }
231         }
232 
getShowBackground()233         boolean getShowBackground() {
234             return mShowBackground;
235         }
236 
refreshItems()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 
hasStableIds()255         public boolean hasStableIds() {
256             return true;
257         }
258 
getCount()259         public int getCount() {
260             return mItems.size();
261         }
262 
263         @Override
isEmpty()264         public boolean isEmpty() {
265             return mState.hasData() && mItems.size() == 0;
266         }
267 
getItem(int position)268         public Object getItem(int position) {
269             return mItems.get(position);
270         }
271 
getItemId(int position)272         public long getItemId(int position) {
273             return mItems.get(position).hashCode();
274         }
275 
areAllItemsEnabled()276         public boolean areAllItemsEnabled() {
277             return false;
278         }
279 
isEnabled(int position)280         public boolean isEnabled(int position) {
281             return !mItems.get(position).mIsProcess;
282         }
283 
getView(int position, View convertView, ViewGroup parent)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 
newView(ViewGroup parent)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 
bindView(View view, int position)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 
refreshUi(boolean dataChanged)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 
onItemClick(AdapterView<?> parent, View v, int position, long id)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
startServiceDetailsActivity(RunningState.MergedItem mi)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                     .setTitleRes(R.string.runningservicedetails_settings_title)
420                     .setSourceMetricsCategory(mOwner.getMetricsCategory())
421                     .launch();
422         }
423     }
424 
onMovedToScrapHeap(View view)425     public void onMovedToScrapHeap(View view) {
426         mActiveItems.remove(view);
427     }
428 
RunningProcessesView(Context context, AttributeSet attrs)429     public RunningProcessesView(Context context, AttributeSet attrs) {
430         super(context, attrs);
431         mMyUserId = UserHandle.myUserId();
432     }
433 
doCreate()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(Utils.getColorAccent(context));
456         mColorBar.setSecondaryProgressTintMode(PorterDuff.Mode.SRC);
457         mColorBar.setProgressBackgroundTintList(
458                 ColorStateList.valueOf(context.getColor(R.color.running_processes_free_ram)));
459         mColorBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC);
460         mBackgroundProcessPrefix = mHeader.findViewById(R.id.freeSizePrefix);
461         mAppsProcessPrefix = mHeader.findViewById(R.id.appsSizePrefix);
462         mForegroundProcessPrefix = mHeader.findViewById(R.id.systemSizePrefix);
463         mBackgroundProcessText = mHeader.findViewById(R.id.freeSize);
464         mAppsProcessText = mHeader.findViewById(R.id.appsSize);
465         mForegroundProcessText = mHeader.findViewById(R.id.systemSize);
466 
467         ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
468         mAm.getMemoryInfo(memInfo);
469         SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
470     }
471 
doPause()472     public void doPause() {
473         mState.pause();
474         mDataAvail = null;
475         mOwner = null;
476     }
477 
doResume(SettingsPreferenceFragment owner, Runnable dataAvail)478     public boolean doResume(SettingsPreferenceFragment owner, Runnable dataAvail) {
479         mOwner = owner;
480         mState.resume(this);
481         if (mState.hasData()) {
482             // If the state already has its data, then let's populate our
483             // list right now to avoid flicker.
484             refreshUi(true);
485             return true;
486         }
487         mDataAvail = dataAvail;
488         return false;
489     }
490 
updateTimes()491     void updateTimes() {
492         Iterator<ActiveItem> it = mActiveItems.values().iterator();
493         while (it.hasNext()) {
494             ActiveItem ai = it.next();
495             if (ai.mRootView.getWindowToken() == null) {
496                 // Clean out any dead views, just in case.
497                 it.remove();
498                 continue;
499             }
500             ai.updateTime(getContext(), mBuilder);
501         }
502     }
503 
504     @Override
onRefreshUi(int what)505     public void onRefreshUi(int what) {
506         switch (what) {
507             case REFRESH_TIME:
508                 updateTimes();
509                 break;
510             case REFRESH_DATA:
511                 refreshUi(false);
512                 updateTimes();
513                 break;
514             case REFRESH_STRUCTURE:
515                 refreshUi(true);
516                 updateTimes();
517                 break;
518         }
519     }
520 }
521