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