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