blob: 5491ad14c6c0e360470a06fa3e1efdf337a796aa [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.BidiFormatter;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.RecyclerListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.util.MemInfoReader;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
public class RunningProcessesView extends FrameLayout
implements AdapterView.OnItemClickListener, RecyclerListener,
RunningState.OnRefreshUiListener {
final int mMyUserId;
long SECONDARY_SERVER_MEM;
final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
ActivityManager mAm;
RunningState mState;
SettingsPreferenceFragment mOwner;
Runnable mDataAvail;
StringBuilder mBuilder = new StringBuilder(128);
RunningState.BaseItem mCurSelected;
ListView mListView;
View mHeader;
ServiceListAdapter mAdapter;
ProgressBar mColorBar;
TextView mBackgroundProcessPrefix;
TextView mAppsProcessPrefix;
TextView mForegroundProcessPrefix;
TextView mBackgroundProcessText;
TextView mAppsProcessText;
TextView mForegroundProcessText;
long mCurTotalRam = -1;
long mCurHighRam = -1; // "System" or "Used"
long mCurMedRam = -1; // "Apps" or "Cached"
long mCurLowRam = -1; // "Free"
boolean mCurShowCached = false;
Dialog mCurDialog;
MemInfoReader mMemInfoReader = new MemInfoReader();
public static class ActiveItem {
View mRootView;
RunningState.BaseItem mItem;
ActivityManager.RunningServiceInfo mService;
ViewHolder mHolder;
long mFirstRunTime;
boolean mSetBackground;
void updateTime(Context context, StringBuilder builder) {
TextView uptimeView = null;
if (mItem instanceof RunningState.ServiceItem) {
// If we are displaying a service, then the service
// uptime goes at the top.
uptimeView = mHolder.size;
} else {
String size = mItem.mSizeStr != null ? mItem.mSizeStr : "";
if (!size.equals(mItem.mCurSizeStr)) {
mItem.mCurSizeStr = size;
mHolder.size.setText(size);
}
if (mItem.mBackground) {
// This is a background process; no uptime.
if (!mSetBackground) {
mSetBackground = true;
mHolder.uptime.setText("");
}
} else if (mItem instanceof RunningState.MergedItem) {
// This item represents both services and processes,
// so show the service uptime below.
uptimeView = mHolder.uptime;
}
}
if (uptimeView != null) {
mSetBackground = false;
if (mFirstRunTime >= 0) {
//Log.i("foo", "Time for " + mItem.mDisplayLabel
// + ": " + (SystemClock.uptimeMillis()-mFirstRunTime));
uptimeView.setText(DateUtils.formatElapsedTime(builder,
(SystemClock.elapsedRealtime()-mFirstRunTime)/1000));
} else {
boolean isService = false;
if (mItem instanceof RunningState.MergedItem) {
isService = ((RunningState.MergedItem)mItem).mServices.size() > 0;
}
if (isService) {
uptimeView.setText(context.getResources().getText(
R.string.service_restarting));
} else {
uptimeView.setText("");
}
}
}
}
}
public static class ViewHolder {
public View rootView;
public ImageView icon;
public TextView name;
public TextView description;
public TextView size;
public TextView uptime;
public ViewHolder(View v) {
rootView = v;
icon = (ImageView)v.findViewById(R.id.icon);
name = (TextView)v.findViewById(R.id.name);
description = (TextView)v.findViewById(R.id.description);
size = (TextView)v.findViewById(R.id.size);
uptime = (TextView)v.findViewById(R.id.uptime);
v.setTag(this);
}
public ActiveItem bind(RunningState state, RunningState.BaseItem item,
StringBuilder builder) {
synchronized (state.mLock) {
PackageManager pm = rootView.getContext().getPackageManager();
if (item.mPackageInfo == null && item instanceof RunningState.MergedItem) {
// Items for background processes don't normally load
// their labels for performance reasons. Do it now.
RunningState.MergedItem mergedItem = (RunningState.MergedItem)item;
if (mergedItem.mProcess != null) {
((RunningState.MergedItem)item).mProcess.ensureLabel(pm);
item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo;
item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel;
}
}
name.setText(item.mDisplayLabel);
ActiveItem ai = new ActiveItem();
ai.mRootView = rootView;
ai.mItem = item;
ai.mHolder = this;
ai.mFirstRunTime = item.mActiveSince;
if (item.mBackground) {
description.setText(rootView.getContext().getText(R.string.cached));
} else {
description.setText(item.mDescription);
}
item.mCurSizeStr = null;
icon.setImageDrawable(item.loadIcon(rootView.getContext(), state));
icon.setVisibility(View.VISIBLE);
ai.updateTime(rootView.getContext(), builder);
return ai;
}
}
}
class ServiceListAdapter extends BaseAdapter {
final RunningState mState;
final LayoutInflater mInflater;
boolean mShowBackground;
ArrayList<RunningState.MergedItem> mOrigItems;
final ArrayList<RunningState.MergedItem> mItems
= new ArrayList<RunningState.MergedItem>();
ServiceListAdapter(RunningState state) {
mState = state;
mInflater = (LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
refreshItems();
}
void setShowBackground(boolean showBackground) {
if (mShowBackground != showBackground) {
mShowBackground = showBackground;
mState.setWatchingBackgroundItems(showBackground);
refreshItems();
refreshUi(true);
}
}
boolean getShowBackground() {
return mShowBackground;
}
void refreshItems() {
ArrayList<RunningState.MergedItem> newItems =
mShowBackground ? mState.getCurrentBackgroundItems()
: mState.getCurrentMergedItems();
if (mOrigItems != newItems) {
mOrigItems = newItems;
if (newItems == null) {
mItems.clear();
} else {
mItems.clear();
mItems.addAll(newItems);
if (mShowBackground) {
Collections.sort(mItems, mState.mBackgroundComparator);
}
}
}
}
public boolean hasStableIds() {
return true;
}
public int getCount() {
return mItems.size();
}
@Override
public boolean isEmpty() {
return mState.hasData() && mItems.size() == 0;
}
public Object getItem(int position) {
return mItems.get(position);
}
public long getItemId(int position) {
return mItems.get(position).hashCode();
}
public boolean areAllItemsEnabled() {
return false;
}
public boolean isEnabled(int position) {
return !mItems.get(position).mIsProcess;
}
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = newView(parent);
} else {
v = convertView;
}
bindView(v, position);
return v;
}
public View newView(ViewGroup parent) {
View v = mInflater.inflate(R.layout.running_processes_item, parent, false);
new ViewHolder(v);
return v;
}
public void bindView(View view, int position) {
synchronized (mState.mLock) {
if (position >= mItems.size()) {
// List must have changed since we last reported its
// size... ignore here, we will be doing a data changed
// to refresh the entire list.
return;
}
ViewHolder vh = (ViewHolder) view.getTag();
RunningState.MergedItem item = mItems.get(position);
ActiveItem ai = vh.bind(mState, item, mBuilder);
mActiveItems.put(view, ai);
}
}
}
void refreshUi(boolean dataChanged) {
if (dataChanged) {
ServiceListAdapter adapter = mAdapter;
adapter.refreshItems();
adapter.notifyDataSetChanged();
}
if (mDataAvail != null) {
mDataAvail.run();
mDataAvail = null;
}
mMemInfoReader.readMemInfo();
/*
// This is the amount of available memory until we start killing
// background services.
long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
- SECONDARY_SERVER_MEM;
if (availMem < 0) {
availMem = 0;
}
*/
synchronized (mState.mLock) {
if (mCurShowCached != mAdapter.mShowBackground) {
mCurShowCached = mAdapter.mShowBackground;
if (mCurShowCached) {
mForegroundProcessPrefix.setText(getResources().getText(
R.string.running_processes_header_used_prefix));
mAppsProcessPrefix.setText(getResources().getText(
R.string.running_processes_header_cached_prefix));
} else {
mForegroundProcessPrefix.setText(getResources().getText(
R.string.running_processes_header_system_prefix));
mAppsProcessPrefix.setText(getResources().getText(
R.string.running_processes_header_apps_prefix));
}
}
final long totalRam = mMemInfoReader.getTotalSize();
final long medRam;
final long lowRam;
if (mCurShowCached) {
lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize();
medRam = mState.mBackgroundProcessMemory;
} else {
lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
+ mState.mBackgroundProcessMemory;
medRam = mState.mServiceProcessMemory;
}
final long highRam = totalRam - medRam - lowRam;
if (mCurTotalRam != totalRam || mCurHighRam != highRam || mCurMedRam != medRam
|| mCurLowRam != lowRam) {
mCurTotalRam = totalRam;
mCurHighRam = highRam;
mCurMedRam = medRam;
mCurLowRam = lowRam;
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
String sizeStr = bidiFormatter.unicodeWrap(
Formatter.formatShortFileSize(getContext(), lowRam));
mBackgroundProcessText.setText(getResources().getString(
R.string.running_processes_header_ram, sizeStr));
sizeStr = bidiFormatter.unicodeWrap(
Formatter.formatShortFileSize(getContext(), medRam));
mAppsProcessText.setText(getResources().getString(
R.string.running_processes_header_ram, sizeStr));
sizeStr = bidiFormatter.unicodeWrap(
Formatter.formatShortFileSize(getContext(), highRam));
mForegroundProcessText.setText(getResources().getString(
R.string.running_processes_header_ram, sizeStr));
int progress = (int) ((highRam/(float) totalRam) * 100);
mColorBar.setProgress(progress);
mColorBar.setSecondaryProgress(progress + (int) ((medRam/(float) totalRam) * 100));
}
}
}
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
ListView l = (ListView)parent;
RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position);
mCurSelected = mi;
startServiceDetailsActivity(mi);
}
// utility method used to start sub activity
private void startServiceDetailsActivity(RunningState.MergedItem mi) {
if (mOwner != null && mi != null) {
// start new fragment to display extended information
Bundle args = new Bundle();
if (mi.mProcess != null) {
args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
}
args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
new SubSettingLauncher(getContext())
.setDestination(RunningServiceDetails.class.getName())
.setArguments(args)
.setTitleRes(R.string.runningservicedetails_settings_title)
.setSourceMetricsCategory(mOwner.getMetricsCategory())
.launch();
}
}
public void onMovedToScrapHeap(View view) {
mActiveItems.remove(view);
}
public RunningProcessesView(Context context, AttributeSet attrs) {
super(context, attrs);
mMyUserId = UserHandle.myUserId();
}
public void doCreate() {
mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
mState = RunningState.getInstance(getContext());
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.running_processes_view, this);
mListView = (ListView)findViewById(android.R.id.list);
View emptyView = findViewById(com.android.internal.R.id.empty);
if (emptyView != null) {
mListView.setEmptyView(emptyView);
}
mListView.setOnItemClickListener(this);
mListView.setRecyclerListener(this);
mAdapter = new ServiceListAdapter(mState);
mListView.setAdapter(mAdapter);
mHeader = inflater.inflate(R.layout.running_processes_header, null);
mListView.addHeaderView(mHeader, null, false /* set as not selectable */);
mColorBar = mHeader.findViewById(R.id.color_bar);
final Context context = getContext();
mColorBar.setProgressTintList(
ColorStateList.valueOf(context.getColor(R.color.running_processes_system_ram)));
mColorBar.setSecondaryProgressTintList(Utils.getColorAccent(context));
mColorBar.setSecondaryProgressTintMode(PorterDuff.Mode.SRC);
mColorBar.setProgressBackgroundTintList(
ColorStateList.valueOf(context.getColor(R.color.running_processes_free_ram)));
mColorBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC);
mBackgroundProcessPrefix = mHeader.findViewById(R.id.freeSizePrefix);
mAppsProcessPrefix = mHeader.findViewById(R.id.appsSizePrefix);
mForegroundProcessPrefix = mHeader.findViewById(R.id.systemSizePrefix);
mBackgroundProcessText = mHeader.findViewById(R.id.freeSize);
mAppsProcessText = mHeader.findViewById(R.id.appsSize);
mForegroundProcessText = mHeader.findViewById(R.id.systemSize);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
mAm.getMemoryInfo(memInfo);
SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
}
public void doPause() {
mState.pause();
mDataAvail = null;
mOwner = null;
}
public boolean doResume(SettingsPreferenceFragment owner, Runnable dataAvail) {
mOwner = owner;
mState.resume(this);
if (mState.hasData()) {
// If the state already has its data, then let's populate our
// list right now to avoid flicker.
refreshUi(true);
return true;
}
mDataAvail = dataAvail;
return false;
}
void updateTimes() {
Iterator<ActiveItem> it = mActiveItems.values().iterator();
while (it.hasNext()) {
ActiveItem ai = it.next();
if (ai.mRootView.getWindowToken() == null) {
// Clean out any dead views, just in case.
it.remove();
continue;
}
ai.updateTime(getContext(), mBuilder);
}
}
@Override
public void onRefreshUi(int what) {
switch (what) {
case REFRESH_TIME:
updateTimes();
break;
case REFRESH_DATA:
refreshUi(false);
updateTimes();
break;
case REFRESH_STRUCTURE:
refreshUi(true);
updateTimes();
break;
}
}
}