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.app.Fragment;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.os.Bundle;
25 import android.os.SystemClock;
26 import android.os.UserHandle;
27 import android.text.BidiFormatter;
28 import android.text.format.DateUtils;
29 import android.text.format.Formatter;
30 import android.util.AttributeSet;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.AbsListView.RecyclerListener;
35 import android.widget.AdapterView;
36 import android.widget.BaseAdapter;
37 import android.widget.FrameLayout;
38 import android.widget.ImageView;
39 import android.widget.ListView;
40 import android.widget.TextView;
41 
42 import com.android.internal.util.MemInfoReader;
43 import com.android.settings.R;
44 import com.android.settings.SettingsActivity;
45 import com.android.settings.Utils;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.HashMap;
50 import java.util.Iterator;
51 
52 public 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 
updateTime(Context context, StringBuilder builder)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 
ViewHolder(View v)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 
bind(RunningState state, RunningState.BaseItem item, StringBuilder builder)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 
ServiceListAdapter(RunningState state)214         ServiceListAdapter(RunningState state) {
215             mState = state;
216             mInflater = (LayoutInflater)getContext().getSystemService(
217                     Context.LAYOUT_INFLATER_SERVICE);
218             refreshItems();
219         }
220 
setShowBackground(boolean showBackground)221         void setShowBackground(boolean showBackground) {
222             if (mShowBackground != showBackground) {
223                 mShowBackground = showBackground;
224                 mState.setWatchingBackgroundItems(showBackground);
225                 refreshItems();
226                 refreshUi(true);
227             }
228         }
229 
getShowBackground()230         boolean getShowBackground() {
231             return mShowBackground;
232         }
233 
refreshItems()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 
hasStableIds()252         public boolean hasStableIds() {
253             return true;
254         }
255 
getCount()256         public int getCount() {
257             return mItems.size();
258         }
259 
260         @Override
isEmpty()261         public boolean isEmpty() {
262             return mState.hasData() && mItems.size() == 0;
263         }
264 
getItem(int position)265         public Object getItem(int position) {
266             return mItems.get(position);
267         }
268 
getItemId(int position)269         public long getItemId(int position) {
270             return mItems.get(position).hashCode();
271         }
272 
areAllItemsEnabled()273         public boolean areAllItemsEnabled() {
274             return false;
275         }
276 
isEnabled(int position)277         public boolean isEnabled(int position) {
278             return !mItems.get(position).mIsProcess;
279         }
280 
getView(int position, View convertView, ViewGroup parent)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 
newView(ViewGroup parent)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 
bindView(View view, int position)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 
refreshUi(boolean dataChanged)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 
onItemClick(AdapterView<?> parent, View v, int position, long id)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
startServiceDetailsActivity(RunningState.MergedItem mi)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 
onMovedToScrapHeap(View view)419     public void onMovedToScrapHeap(View view) {
420         mActiveItems.remove(view);
421     }
422 
RunningProcessesView(Context context, AttributeSet attrs)423     public RunningProcessesView(Context context, AttributeSet attrs) {
424         super(context, attrs);
425         mMyUserId = UserHandle.myUserId();
426     }
427 
doCreate()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 
doPause()462     public void doPause() {
463         mState.pause();
464         mDataAvail = null;
465         mOwner = null;
466     }
467 
doResume(Fragment owner, Runnable dataAvail)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 
updateTimes()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
onRefreshUi(int what)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