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