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