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.os.UserHandle; 30 import android.preference.PreferenceActivity; 31 import android.text.format.DateUtils; 32 import android.text.format.Formatter; 33 import android.util.AttributeSet; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.AdapterView; 38 import android.widget.BaseAdapter; 39 import android.widget.FrameLayout; 40 import android.widget.ImageView; 41 import android.widget.ListView; 42 import android.widget.TextView; 43 import android.widget.AbsListView.RecyclerListener; 44 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.HashMap; 48 import java.util.Iterator; 49 50 public class RunningProcessesView extends FrameLayout 51 implements AdapterView.OnItemClickListener, RecyclerListener, 52 RunningState.OnRefreshUiListener { 53 54 final int mMyUserId; 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 MemInfoReader mMemInfoReader = new MemInfoReader(); 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 mergedItem = (RunningState.MergedItem)item; 175 if (mergedItem.mProcess != null) { 176 ((RunningState.MergedItem)item).mProcess.ensureLabel(pm); 177 item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo; 178 item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel; 179 } 180 } 181 name.setText(item.mDisplayLabel); 182 ActiveItem ai = new ActiveItem(); 183 ai.mRootView = rootView; 184 ai.mItem = item; 185 ai.mHolder = this; 186 ai.mFirstRunTime = item.mActiveSince; 187 if (item.mBackground) { 188 description.setText(rootView.getContext().getText(R.string.cached)); 189 } else { 190 description.setText(item.mDescription); 191 } 192 item.mCurSizeStr = null; 193 icon.setImageDrawable(item.loadIcon(rootView.getContext(), state)); 194 icon.setVisibility(View.VISIBLE); 195 ai.updateTime(rootView.getContext(), builder); 196 return ai; 197 } 198 } 199 } 200 201 static class TimeTicker extends TextView { 202 public TimeTicker(Context context, AttributeSet attrs) { 203 super(context, attrs); 204 } 205 } 206 207 class ServiceListAdapter extends BaseAdapter { 208 final RunningState mState; 209 final LayoutInflater mInflater; 210 boolean mShowBackground; 211 ArrayList<RunningState.MergedItem> mOrigItems; 212 final ArrayList<RunningState.MergedItem> mItems 213 = new ArrayList<RunningState.MergedItem>(); 214 215 ServiceListAdapter(RunningState state) { 216 mState = state; 217 mInflater = (LayoutInflater)getContext().getSystemService( 218 Context.LAYOUT_INFLATER_SERVICE); 219 refreshItems(); 220 } 221 222 void setShowBackground(boolean showBackground) { 223 if (mShowBackground != showBackground) { 224 mShowBackground = showBackground; 225 mState.setWatchingBackgroundItems(showBackground); 226 refreshItems(); 227 notifyDataSetChanged(); 228 mColorBar.setShowingGreen(mShowBackground); 229 } 230 } 231 232 boolean getShowBackground() { 233 return mShowBackground; 234 } 235 236 void refreshItems() { 237 ArrayList<RunningState.MergedItem> newItems = 238 mShowBackground ? mState.getCurrentBackgroundItems() 239 : mState.getCurrentMergedItems(); 240 if (mOrigItems != newItems) { 241 mOrigItems = newItems; 242 if (newItems == null) { 243 mItems.clear(); 244 } else { 245 mItems.clear(); 246 mItems.addAll(newItems); 247 if (mShowBackground) { 248 Collections.sort(mItems, mState.mBackgroundComparator); 249 } 250 } 251 } 252 } 253 254 public boolean hasStableIds() { 255 return true; 256 } 257 258 public int getCount() { 259 return mItems.size(); 260 } 261 262 @Override 263 public boolean isEmpty() { 264 return mState.hasData() && mItems.size() == 0; 265 } 266 267 public Object getItem(int position) { 268 return mItems.get(position); 269 } 270 271 public long getItemId(int position) { 272 return mItems.get(position).hashCode(); 273 } 274 275 public boolean areAllItemsEnabled() { 276 return false; 277 } 278 279 public boolean isEnabled(int position) { 280 return !mItems.get(position).mIsProcess; 281 } 282 283 public View getView(int position, View convertView, ViewGroup parent) { 284 View v; 285 if (convertView == null) { 286 v = newView(parent); 287 } else { 288 v = convertView; 289 } 290 bindView(v, position); 291 return v; 292 } 293 294 public View newView(ViewGroup parent) { 295 View v = mInflater.inflate(R.layout.running_processes_item, parent, false); 296 new ViewHolder(v); 297 return v; 298 } 299 300 public void bindView(View view, int position) { 301 synchronized (mState.mLock) { 302 if (position >= mItems.size()) { 303 // List must have changed since we last reported its 304 // size... ignore here, we will be doing a data changed 305 // to refresh the entire list. 306 return; 307 } 308 ViewHolder vh = (ViewHolder) view.getTag(); 309 RunningState.MergedItem item = mItems.get(position); 310 ActiveItem ai = vh.bind(mState, item, mBuilder); 311 mActiveItems.put(view, ai); 312 } 313 } 314 } 315 316 void refreshUi(boolean dataChanged) { 317 if (dataChanged) { 318 ServiceListAdapter adapter = (ServiceListAdapter)(mListView.getAdapter()); 319 adapter.refreshItems(); 320 adapter.notifyDataSetChanged(); 321 } 322 323 if (mDataAvail != null) { 324 mDataAvail.run(); 325 mDataAvail = null; 326 } 327 328 // This is the amount of available memory until we start killing 329 // background services. 330 mMemInfoReader.readMemInfo(); 331 long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize() 332 - SECONDARY_SERVER_MEM; 333 if (availMem < 0) { 334 availMem = 0; 335 } 336 337 synchronized (mState.mLock) { 338 if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses 339 || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory 340 || mLastAvailMemory != availMem) { 341 mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses; 342 mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory; 343 mLastAvailMemory = availMem; 344 long freeMem = mLastAvailMemory + mLastBackgroundProcessMemory; 345 String sizeStr = Formatter.formatShortFileSize(getContext(), freeMem); 346 mBackgroundProcessText.setText(getResources().getString( 347 R.string.service_background_processes, sizeStr)); 348 sizeStr = Formatter.formatShortFileSize(getContext(), 349 mMemInfoReader.getTotalSize() - freeMem); 350 mForegroundProcessText.setText(getResources().getString( 351 R.string.service_foreground_processes, sizeStr)); 352 } 353 if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses 354 || mLastForegroundProcessMemory != mState.mForegroundProcessMemory 355 || mLastNumServiceProcesses != mState.mNumServiceProcesses 356 || mLastServiceProcessMemory != mState.mServiceProcessMemory) { 357 mLastNumForegroundProcesses = mState.mNumForegroundProcesses; 358 mLastForegroundProcessMemory = mState.mForegroundProcessMemory; 359 mLastNumServiceProcesses = mState.mNumServiceProcesses; 360 mLastServiceProcessMemory = mState.mServiceProcessMemory; 361 /* 362 String sizeStr = Formatter.formatShortFileSize(getContext(), 363 mLastForegroundProcessMemory + mLastServiceProcessMemory); 364 mForegroundProcessText.setText(getResources().getString( 365 R.string.service_foreground_processes, sizeStr)); 366 */ 367 } 368 369 float totalMem = mMemInfoReader.getTotalSize(); 370 float totalShownMem = availMem + mLastBackgroundProcessMemory 371 + mLastServiceProcessMemory; 372 mColorBar.setRatios((totalMem-totalShownMem)/totalMem, 373 mLastServiceProcessMemory/totalMem, 374 mLastBackgroundProcessMemory/totalMem); 375 } 376 } 377 378 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 379 ListView l = (ListView)parent; 380 RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position); 381 mCurSelected = mi; 382 startServiceDetailsActivity(mi); 383 } 384 385 // utility method used to start sub activity 386 private void startServiceDetailsActivity(RunningState.MergedItem mi) { 387 if (mOwner != null) { 388 // start new fragment to display extended information 389 Bundle args = new Bundle(); 390 if (mi.mProcess != null) { 391 args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid); 392 args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName); 393 } 394 args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId); 395 args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground); 396 397 PreferenceActivity pa = (PreferenceActivity)mOwner.getActivity(); 398 pa.startPreferencePanel(RunningServiceDetails.class.getName(), args, 399 R.string.runningservicedetails_settings_title, null, null, 0); 400 } 401 } 402 403 public void onMovedToScrapHeap(View view) { 404 mActiveItems.remove(view); 405 } 406 407 public RunningProcessesView(Context context, AttributeSet attrs) { 408 super(context, attrs); 409 mMyUserId = UserHandle.myUserId(); 410 } 411 412 public void doCreate(Bundle savedInstanceState) { 413 mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); 414 mState = RunningState.getInstance(getContext()); 415 LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( 416 Context.LAYOUT_INFLATER_SERVICE); 417 inflater.inflate(R.layout.running_processes_view, this); 418 mListView = (ListView)findViewById(android.R.id.list); 419 View emptyView = findViewById(com.android.internal.R.id.empty); 420 if (emptyView != null) { 421 mListView.setEmptyView(emptyView); 422 } 423 mListView.setOnItemClickListener(this); 424 mListView.setRecyclerListener(this); 425 mAdapter = new ServiceListAdapter(mState); 426 mListView.setAdapter(mAdapter); 427 mColorBar = (LinearColorBar)findViewById(R.id.color_bar); 428 mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText); 429 mBackgroundProcessText.setOnClickListener(new View.OnClickListener() { 430 @Override 431 public void onClick(View v) { 432 mAdapter.setShowBackground(true); 433 } 434 }); 435 mForegroundProcessText = (TextView)findViewById(R.id.foregroundText); 436 mForegroundProcessText.setOnClickListener(new View.OnClickListener() { 437 @Override 438 public void onClick(View v) { 439 mAdapter.setShowBackground(false); 440 } 441 }); 442 443 ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); 444 mAm.getMemoryInfo(memInfo); 445 SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold; 446 } 447 448 public void doPause() { 449 mState.pause(); 450 mDataAvail = null; 451 mOwner = null; 452 } 453 454 public boolean doResume(Fragment owner, Runnable dataAvail) { 455 mOwner = owner; 456 mState.resume(this); 457 if (mState.hasData()) { 458 // If the state already has its data, then let's populate our 459 // list right now to avoid flicker. 460 refreshUi(true); 461 return true; 462 } 463 mDataAvail = dataAvail; 464 return false; 465 } 466 467 void updateTimes() { 468 Iterator<ActiveItem> it = mActiveItems.values().iterator(); 469 while (it.hasNext()) { 470 ActiveItem ai = it.next(); 471 if (ai.mRootView.getWindowToken() == null) { 472 // Clean out any dead views, just in case. 473 it.remove(); 474 continue; 475 } 476 ai.updateTime(getContext(), mBuilder); 477 } 478 } 479 480 @Override 481 public void onRefreshUi(int what) { 482 switch (what) { 483 case REFRESH_TIME: 484 updateTimes(); 485 break; 486 case REFRESH_DATA: 487 refreshUi(false); 488 updateTimes(); 489 break; 490 case REFRESH_STRUCTURE: 491 refreshUi(true); 492 updateTimes(); 493 break; 494 } 495 } 496 } 497