/* * 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 mActiveItems = new HashMap(); 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 mOrigItems; final ArrayList mItems = new ArrayList(); 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 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 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; } } }