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 com.android.internal.util.MemInfoReader; 20 import com.android.settings.R; 21 22 import android.app.ActivityManager; 23 import android.app.Dialog; 24 import android.app.Fragment; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.os.Bundle; 28 import android.os.SystemClock; 29 import android.preference.PreferenceActivity; 30 import android.text.format.DateUtils; 31 import android.text.format.Formatter; 32 import android.util.AttributeSet; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 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.TextView; 42 import android.widget.AbsListView.RecyclerListener; 43 44 import java.util.ArrayList; 45 import java.util.HashMap; 46 import java.util.Iterator; 47 48 public class RunningProcessesView extends FrameLayout 49 implements AdapterView.OnItemClickListener, RecyclerListener, 50 RunningState.OnRefreshUiListener { 51 52 long SECONDARY_SERVER_MEM; 53 54 final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>(); 55 56 ActivityManager mAm; 57 58 RunningState mState; 59 60 Fragment mOwner; 61 62 Runnable mDataAvail; 63 64 StringBuilder mBuilder = new StringBuilder(128); 65 66 RunningState.BaseItem mCurSelected; 67 68 ListView mListView; 69 ServiceListAdapter mAdapter; 70 LinearColorBar mColorBar; 71 TextView mBackgroundProcessText; 72 TextView mForegroundProcessText; 73 74 int mLastNumBackgroundProcesses = -1; 75 int mLastNumForegroundProcesses = -1; 76 int mLastNumServiceProcesses = -1; 77 long mLastBackgroundProcessMemory = -1; 78 long mLastForegroundProcessMemory = -1; 79 long mLastServiceProcessMemory = -1; 80 long mLastAvailMemory = -1; 81 82 Dialog mCurDialog; 83 84 MemInfoReader mMemInfoReader = new MemInfoReader(); 85 86 public static class ActiveItem { 87 View mRootView; 88 RunningState.BaseItem mItem; 89 ActivityManager.RunningServiceInfo mService; 90 ViewHolder mHolder; 91 long mFirstRunTime; 92 boolean mSetBackground; 93 94 void updateTime(Context context, StringBuilder builder) { 95 TextView uptimeView = null; 96 97 if (mItem instanceof RunningState.ServiceItem) { 98 // If we are displaying a service, then the service 99 // uptime goes at the top. 100 uptimeView = mHolder.size; 101 102 } else { 103 String size = mItem.mSizeStr != null ? mItem.mSizeStr : ""; 104 if (!size.equals(mItem.mCurSizeStr)) { 105 mItem.mCurSizeStr = size; 106 mHolder.size.setText(size); 107 } 108 109 if (mItem.mBackground) { 110 // This is a background process; no uptime. 111 if (!mSetBackground) { 112 mSetBackground = true; 113 mHolder.uptime.setText(""); 114 } 115 } else if (mItem instanceof RunningState.MergedItem) { 116 // This item represents both services and processes, 117 // so show the service uptime below. 118 uptimeView = mHolder.uptime; 119 } 120 } 121 122 if (uptimeView != null) { 123 mSetBackground = false; 124 if (mFirstRunTime >= 0) { 125 //Log.i("foo", "Time for " + mItem.mDisplayLabel 126 // + ": " + (SystemClock.uptimeMillis()-mFirstRunTime)); 127 uptimeView.setText(DateUtils.formatElapsedTime(builder, 128 (SystemClock.elapsedRealtime()-mFirstRunTime)/1000)); 129 } else { 130 boolean isService = false; 131 if (mItem instanceof RunningState.MergedItem) { 132 isService = ((RunningState.MergedItem)mItem).mServices.size() > 0; 133 } 134 if (isService) { 135 uptimeView.setText(context.getResources().getText( 136 R.string.service_restarting)); 137 } else { 138 uptimeView.setText(""); 139 } 140 } 141 } 142 } 143 } 144 145 public static class ViewHolder { 146 public View rootView; 147 public ImageView icon; 148 public TextView name; 149 public TextView description; 150 public TextView size; 151 public TextView uptime; 152 153 public ViewHolder(View v) { 154 rootView = v; 155 icon = (ImageView)v.findViewById(R.id.icon); 156 name = (TextView)v.findViewById(R.id.name); 157 description = (TextView)v.findViewById(R.id.description); 158 size = (TextView)v.findViewById(R.id.size); 159 uptime = (TextView)v.findViewById(R.id.uptime); 160 v.setTag(this); 161 } 162 163 public ActiveItem bind(RunningState state, RunningState.BaseItem item, 164 StringBuilder builder) { 165 synchronized (state.mLock) { 166 PackageManager pm = rootView.getContext().getPackageManager(); 167 if (item.mPackageInfo == null && item instanceof RunningState.MergedItem) { 168 // Items for background processes don't normally load 169 // their labels for performance reasons. Do it now. 170 ((RunningState.MergedItem)item).mProcess.ensureLabel(pm); 171 item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo; 172 item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel; 173 } 174 name.setText(item.mDisplayLabel); 175 ActiveItem ai = new ActiveItem(); 176 ai.mRootView = rootView; 177 ai.mItem = item; 178 ai.mHolder = this; 179 ai.mFirstRunTime = item.mActiveSince; 180 if (item.mBackground) { 181 description.setText(rootView.getContext().getText(R.string.cached)); 182 } else { 183 description.setText(item.mDescription); 184 } 185 item.mCurSizeStr = null; 186 if (item.mPackageInfo != null) { 187 icon.setImageDrawable(item.mPackageInfo.loadIcon(pm)); 188 } 189 icon.setVisibility(View.VISIBLE); 190 ai.updateTime(rootView.getContext(), builder); 191 return ai; 192 } 193 } 194 } 195 196 static class TimeTicker extends TextView { 197 public TimeTicker(Context context, AttributeSet attrs) { 198 super(context, attrs); 199 } 200 } 201 202 class ServiceListAdapter extends BaseAdapter { 203 final RunningState mState; 204 final LayoutInflater mInflater; 205 boolean mShowBackground; 206 ArrayList<RunningState.MergedItem> mItems; 207 208 ServiceListAdapter(RunningState state) { 209 mState = state; 210 mInflater = (LayoutInflater)getContext().getSystemService( 211 Context.LAYOUT_INFLATER_SERVICE); 212 refreshItems(); 213 } 214 215 void setShowBackground(boolean showBackground) { 216 if (mShowBackground != showBackground) { 217 mShowBackground = showBackground; 218 mState.setWatchingBackgroundItems(showBackground); 219 refreshItems(); 220 notifyDataSetChanged(); 221 mColorBar.setShowingGreen(mShowBackground); 222 } 223 } 224 225 boolean getShowBackground() { 226 return mShowBackground; 227 } 228 229 void refreshItems() { 230 ArrayList<RunningState.MergedItem> newItems = 231 mShowBackground ? mState.getCurrentBackgroundItems() 232 : mState.getCurrentMergedItems(); 233 if (mItems != newItems) { 234 mItems = newItems; 235 } 236 if (mItems == null) { 237 mItems = new ArrayList<RunningState.MergedItem>(); 238 } 239 } 240 241 public boolean hasStableIds() { 242 return true; 243 } 244 245 public int getCount() { 246 return mItems.size(); 247 } 248 249 @Override 250 public boolean isEmpty() { 251 return mState.hasData() && mItems.size() == 0; 252 } 253 254 public Object getItem(int position) { 255 return mItems.get(position); 256 } 257 258 public long getItemId(int position) { 259 return mItems.get(position).hashCode(); 260 } 261 262 public boolean areAllItemsEnabled() { 263 return false; 264 } 265 266 public boolean isEnabled(int position) { 267 return !mItems.get(position).mIsProcess; 268 } 269 270 public View getView(int position, View convertView, ViewGroup parent) { 271 View v; 272 if (convertView == null) { 273 v = newView(parent); 274 } else { 275 v = convertView; 276 } 277 bindView(v, position); 278 return v; 279 } 280 281 public View newView(ViewGroup parent) { 282 View v = mInflater.inflate(R.layout.running_processes_item, parent, false); 283 new ViewHolder(v); 284 return v; 285 } 286 287 public void bindView(View view, int position) { 288 synchronized (mState.mLock) { 289 if (position >= mItems.size()) { 290 // List must have changed since we last reported its 291 // size... ignore here, we will be doing a data changed 292 // to refresh the entire list. 293 return; 294 } 295 ViewHolder vh = (ViewHolder) view.getTag(); 296 RunningState.MergedItem item = mItems.get(position); 297 ActiveItem ai = vh.bind(mState, item, mBuilder); 298 mActiveItems.put(view, ai); 299 } 300 } 301 } 302 303 void refreshUi(boolean dataChanged) { 304 if (dataChanged) { 305 ServiceListAdapter adapter = (ServiceListAdapter)(mListView.getAdapter()); 306 adapter.refreshItems(); 307 adapter.notifyDataSetChanged(); 308 } 309 310 if (mDataAvail != null) { 311 mDataAvail.run(); 312 mDataAvail = null; 313 } 314 315 // This is the amount of available memory until we start killing 316 // background services. 317 mMemInfoReader.readMemInfo(); 318 long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize() 319 - SECONDARY_SERVER_MEM; 320 if (availMem < 0) { 321 availMem = 0; 322 } 323 324 synchronized (mState.mLock) { 325 if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses 326 || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory 327 || mLastAvailMemory != availMem) { 328 mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses; 329 mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory; 330 mLastAvailMemory = availMem; 331 long freeMem = mLastAvailMemory + mLastBackgroundProcessMemory; 332 String sizeStr = Formatter.formatShortFileSize(getContext(), freeMem); 333 mBackgroundProcessText.setText(getResources().getString( 334 R.string.service_background_processes, sizeStr)); 335 sizeStr = Formatter.formatShortFileSize(getContext(), 336 mMemInfoReader.getTotalSize() - freeMem); 337 mForegroundProcessText.setText(getResources().getString( 338 R.string.service_foreground_processes, sizeStr)); 339 } 340 if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses 341 || mLastForegroundProcessMemory != mState.mForegroundProcessMemory 342 || mLastNumServiceProcesses != mState.mNumServiceProcesses 343 || mLastServiceProcessMemory != mState.mServiceProcessMemory) { 344 mLastNumForegroundProcesses = mState.mNumForegroundProcesses; 345 mLastForegroundProcessMemory = mState.mForegroundProcessMemory; 346 mLastNumServiceProcesses = mState.mNumServiceProcesses; 347 mLastServiceProcessMemory = mState.mServiceProcessMemory; 348 /* 349 String sizeStr = Formatter.formatShortFileSize(getContext(), 350 mLastForegroundProcessMemory + mLastServiceProcessMemory); 351 mForegroundProcessText.setText(getResources().getString( 352 R.string.service_foreground_processes, sizeStr)); 353 */ 354 } 355 356 float totalMem = mMemInfoReader.getTotalSize(); 357 float totalShownMem = availMem + mLastBackgroundProcessMemory 358 + mLastServiceProcessMemory; 359 mColorBar.setRatios((totalMem-totalShownMem)/totalMem, 360 mLastServiceProcessMemory/totalMem, 361 mLastBackgroundProcessMemory/totalMem); 362 } 363 } 364 365 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 366 ListView l = (ListView)parent; 367 RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position); 368 mCurSelected = mi; 369 startServiceDetailsActivity(mi); 370 } 371 372 // utility method used to start sub activity 373 private void startServiceDetailsActivity(RunningState.MergedItem mi) { 374 if (mOwner != null) { 375 // start new fragment to display extended information 376 Bundle args = new Bundle(); 377 args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid); 378 args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName); 379 args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground); 380 381 PreferenceActivity pa = (PreferenceActivity)mOwner.getActivity(); 382 pa.startPreferencePanel(RunningServiceDetails.class.getName(), args, 383 R.string.runningservicedetails_settings_title, null, null, 0); 384 } 385 } 386 387 public void onMovedToScrapHeap(View view) { 388 mActiveItems.remove(view); 389 } 390 391 public RunningProcessesView(Context context, AttributeSet attrs) { 392 super(context, attrs); 393 } 394 395 public void doCreate(Bundle savedInstanceState) { 396 mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); 397 mState = RunningState.getInstance(getContext()); 398 LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( 399 Context.LAYOUT_INFLATER_SERVICE); 400 inflater.inflate(R.layout.running_processes_view, this); 401 mListView = (ListView)findViewById(android.R.id.list); 402 View emptyView = findViewById(com.android.internal.R.id.empty); 403 if (emptyView != null) { 404 mListView.setEmptyView(emptyView); 405 } 406 mListView.setOnItemClickListener(this); 407 mListView.setRecyclerListener(this); 408 mAdapter = new ServiceListAdapter(mState); 409 mListView.setAdapter(mAdapter); 410 mColorBar = (LinearColorBar)findViewById(R.id.color_bar); 411 mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText); 412 mBackgroundProcessText.setOnClickListener(new View.OnClickListener() { 413 @Override 414 public void onClick(View v) { 415 mAdapter.setShowBackground(true); 416 } 417 }); 418 mForegroundProcessText = (TextView)findViewById(R.id.foregroundText); 419 mForegroundProcessText.setOnClickListener(new View.OnClickListener() { 420 @Override 421 public void onClick(View v) { 422 mAdapter.setShowBackground(false); 423 } 424 }); 425 426 ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); 427 mAm.getMemoryInfo(memInfo); 428 SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold; 429 } 430 431 public void doPause() { 432 mState.pause(); 433 mDataAvail = null; 434 mOwner = null; 435 } 436 437 public boolean doResume(Fragment owner, Runnable dataAvail) { 438 mOwner = owner; 439 mState.resume(this); 440 if (mState.hasData()) { 441 // If the state already has its data, then let's populate our 442 // list right now to avoid flicker. 443 refreshUi(true); 444 return true; 445 } 446 mDataAvail = dataAvail; 447 return false; 448 } 449 450 void updateTimes() { 451 Iterator<ActiveItem> it = mActiveItems.values().iterator(); 452 while (it.hasNext()) { 453 ActiveItem ai = it.next(); 454 if (ai.mRootView.getWindowToken() == null) { 455 // Clean out any dead views, just in case. 456 it.remove(); 457 continue; 458 } 459 ai.updateTime(getContext(), mBuilder); 460 } 461 } 462 463 @Override 464 public void onRefreshUi(int what) { 465 switch (what) { 466 case REFRESH_TIME: 467 updateTimes(); 468 break; 469 case REFRESH_DATA: 470 refreshUi(false); 471 updateTimes(); 472 break; 473 case REFRESH_STRUCTURE: 474 refreshUi(true); 475 updateTimes(); 476 break; 477 } 478 } 479 } 480