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