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 import com.android.settings.Utils; 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 updateTime(Context context, StringBuilder builder)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 ViewHolder(View v)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 bind(RunningState state, RunningState.BaseItem item, StringBuilder builder)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 class ServiceListAdapter extends BaseAdapter { 207 final RunningState mState; 208 final LayoutInflater mInflater; 209 boolean mShowBackground; 210 ArrayList<RunningState.MergedItem> mOrigItems; 211 final ArrayList<RunningState.MergedItem> mItems 212 = new ArrayList<RunningState.MergedItem>(); 213 ServiceListAdapter(RunningState state)214 ServiceListAdapter(RunningState state) { 215 mState = state; 216 mInflater = (LayoutInflater)getContext().getSystemService( 217 Context.LAYOUT_INFLATER_SERVICE); 218 refreshItems(); 219 } 220 setShowBackground(boolean showBackground)221 void setShowBackground(boolean showBackground) { 222 if (mShowBackground != showBackground) { 223 mShowBackground = showBackground; 224 mState.setWatchingBackgroundItems(showBackground); 225 refreshItems(); 226 refreshUi(true); 227 } 228 } 229 getShowBackground()230 boolean getShowBackground() { 231 return mShowBackground; 232 } 233 refreshItems()234 void refreshItems() { 235 ArrayList<RunningState.MergedItem> newItems = 236 mShowBackground ? mState.getCurrentBackgroundItems() 237 : mState.getCurrentMergedItems(); 238 if (mOrigItems != newItems) { 239 mOrigItems = newItems; 240 if (newItems == null) { 241 mItems.clear(); 242 } else { 243 mItems.clear(); 244 mItems.addAll(newItems); 245 if (mShowBackground) { 246 Collections.sort(mItems, mState.mBackgroundComparator); 247 } 248 } 249 } 250 } 251 hasStableIds()252 public boolean hasStableIds() { 253 return true; 254 } 255 getCount()256 public int getCount() { 257 return mItems.size(); 258 } 259 260 @Override isEmpty()261 public boolean isEmpty() { 262 return mState.hasData() && mItems.size() == 0; 263 } 264 getItem(int position)265 public Object getItem(int position) { 266 return mItems.get(position); 267 } 268 getItemId(int position)269 public long getItemId(int position) { 270 return mItems.get(position).hashCode(); 271 } 272 areAllItemsEnabled()273 public boolean areAllItemsEnabled() { 274 return false; 275 } 276 isEnabled(int position)277 public boolean isEnabled(int position) { 278 return !mItems.get(position).mIsProcess; 279 } 280 getView(int position, View convertView, ViewGroup parent)281 public View getView(int position, View convertView, ViewGroup parent) { 282 View v; 283 if (convertView == null) { 284 v = newView(parent); 285 } else { 286 v = convertView; 287 } 288 bindView(v, position); 289 return v; 290 } 291 newView(ViewGroup parent)292 public View newView(ViewGroup parent) { 293 View v = mInflater.inflate(R.layout.running_processes_item, parent, false); 294 new ViewHolder(v); 295 return v; 296 } 297 bindView(View view, int position)298 public void bindView(View view, int position) { 299 synchronized (mState.mLock) { 300 if (position >= mItems.size()) { 301 // List must have changed since we last reported its 302 // size... ignore here, we will be doing a data changed 303 // to refresh the entire list. 304 return; 305 } 306 ViewHolder vh = (ViewHolder) view.getTag(); 307 RunningState.MergedItem item = mItems.get(position); 308 ActiveItem ai = vh.bind(mState, item, mBuilder); 309 mActiveItems.put(view, ai); 310 } 311 } 312 } 313 refreshUi(boolean dataChanged)314 void refreshUi(boolean dataChanged) { 315 if (dataChanged) { 316 ServiceListAdapter adapter = mAdapter; 317 adapter.refreshItems(); 318 adapter.notifyDataSetChanged(); 319 } 320 321 if (mDataAvail != null) { 322 mDataAvail.run(); 323 mDataAvail = null; 324 } 325 326 mMemInfoReader.readMemInfo(); 327 328 /* 329 // This is the amount of available memory until we start killing 330 // background services. 331 long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize() 332 - SECONDARY_SERVER_MEM; 333 if (availMem < 0) { 334 availMem = 0; 335 } 336 */ 337 338 synchronized (mState.mLock) { 339 if (mCurShowCached != mAdapter.mShowBackground) { 340 mCurShowCached = mAdapter.mShowBackground; 341 if (mCurShowCached) { 342 mForegroundProcessPrefix.setText(getResources().getText( 343 R.string.running_processes_header_used_prefix)); 344 mAppsProcessPrefix.setText(getResources().getText( 345 R.string.running_processes_header_cached_prefix)); 346 } else { 347 mForegroundProcessPrefix.setText(getResources().getText( 348 R.string.running_processes_header_system_prefix)); 349 mAppsProcessPrefix.setText(getResources().getText( 350 R.string.running_processes_header_apps_prefix)); 351 } 352 } 353 354 final long totalRam = mMemInfoReader.getTotalSize(); 355 final long medRam; 356 final long lowRam; 357 if (mCurShowCached) { 358 lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize(); 359 medRam = mState.mBackgroundProcessMemory; 360 } else { 361 lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize() 362 + mState.mBackgroundProcessMemory; 363 medRam = mState.mServiceProcessMemory; 364 365 } 366 final long highRam = totalRam - medRam - lowRam; 367 368 if (mCurTotalRam != totalRam || mCurHighRam != highRam || mCurMedRam != medRam 369 || mCurLowRam != lowRam) { 370 mCurTotalRam = totalRam; 371 mCurHighRam = highRam; 372 mCurMedRam = medRam; 373 mCurLowRam = lowRam; 374 BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 375 String sizeStr = bidiFormatter.unicodeWrap( 376 Formatter.formatShortFileSize(getContext(), lowRam)); 377 mBackgroundProcessText.setText(getResources().getString( 378 R.string.running_processes_header_ram, sizeStr)); 379 sizeStr = bidiFormatter.unicodeWrap( 380 Formatter.formatShortFileSize(getContext(), medRam)); 381 mAppsProcessText.setText(getResources().getString( 382 R.string.running_processes_header_ram, sizeStr)); 383 sizeStr = bidiFormatter.unicodeWrap( 384 Formatter.formatShortFileSize(getContext(), highRam)); 385 mForegroundProcessText.setText(getResources().getString( 386 R.string.running_processes_header_ram, sizeStr)); 387 mColorBar.setRatios(highRam/(float)totalRam, 388 medRam/(float)totalRam, 389 lowRam/(float)totalRam); 390 } 391 } 392 } 393 onItemClick(AdapterView<?> parent, View v, int position, long id)394 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 395 ListView l = (ListView)parent; 396 RunningState.MergedItem mi = (RunningState.MergedItem)l.getAdapter().getItem(position); 397 mCurSelected = mi; 398 startServiceDetailsActivity(mi); 399 } 400 401 // utility method used to start sub activity startServiceDetailsActivity(RunningState.MergedItem mi)402 private void startServiceDetailsActivity(RunningState.MergedItem mi) { 403 if (mOwner != null && mi != null) { 404 // start new fragment to display extended information 405 Bundle args = new Bundle(); 406 if (mi.mProcess != null) { 407 args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid); 408 args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName); 409 } 410 args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId); 411 args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground); 412 413 SettingsActivity sa = (SettingsActivity) mOwner.getActivity(); 414 sa.startPreferencePanel(mOwner, RunningServiceDetails.class.getName(), args, 415 R.string.runningservicedetails_settings_title, null, null, 0); 416 } 417 } 418 onMovedToScrapHeap(View view)419 public void onMovedToScrapHeap(View view) { 420 mActiveItems.remove(view); 421 } 422 RunningProcessesView(Context context, AttributeSet attrs)423 public RunningProcessesView(Context context, AttributeSet attrs) { 424 super(context, attrs); 425 mMyUserId = UserHandle.myUserId(); 426 } 427 doCreate()428 public void doCreate() { 429 mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); 430 mState = RunningState.getInstance(getContext()); 431 LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( 432 Context.LAYOUT_INFLATER_SERVICE); 433 inflater.inflate(R.layout.running_processes_view, this); 434 mListView = (ListView)findViewById(android.R.id.list); 435 View emptyView = findViewById(com.android.internal.R.id.empty); 436 if (emptyView != null) { 437 mListView.setEmptyView(emptyView); 438 } 439 mListView.setOnItemClickListener(this); 440 mListView.setRecyclerListener(this); 441 mAdapter = new ServiceListAdapter(mState); 442 mListView.setAdapter(mAdapter); 443 mHeader = inflater.inflate(R.layout.running_processes_header, null); 444 mListView.addHeaderView(mHeader, null, false /* set as not selectable */); 445 mColorBar = (LinearColorBar)mHeader.findViewById(R.id.color_bar); 446 final Context context = getContext(); 447 mColorBar.setColors(context.getColor(R.color.running_processes_system_ram), 448 Utils.getColorAccent(context), 449 context.getColor(R.color.running_processes_free_ram)); 450 mBackgroundProcessPrefix = (TextView)mHeader.findViewById(R.id.freeSizePrefix); 451 mAppsProcessPrefix = (TextView)mHeader.findViewById(R.id.appsSizePrefix); 452 mForegroundProcessPrefix = (TextView)mHeader.findViewById(R.id.systemSizePrefix); 453 mBackgroundProcessText = (TextView)mHeader.findViewById(R.id.freeSize); 454 mAppsProcessText = (TextView)mHeader.findViewById(R.id.appsSize); 455 mForegroundProcessText = (TextView)mHeader.findViewById(R.id.systemSize); 456 457 ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); 458 mAm.getMemoryInfo(memInfo); 459 SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold; 460 } 461 doPause()462 public void doPause() { 463 mState.pause(); 464 mDataAvail = null; 465 mOwner = null; 466 } 467 doResume(Fragment owner, Runnable dataAvail)468 public boolean doResume(Fragment owner, Runnable dataAvail) { 469 mOwner = owner; 470 mState.resume(this); 471 if (mState.hasData()) { 472 // If the state already has its data, then let's populate our 473 // list right now to avoid flicker. 474 refreshUi(true); 475 return true; 476 } 477 mDataAvail = dataAvail; 478 return false; 479 } 480 updateTimes()481 void updateTimes() { 482 Iterator<ActiveItem> it = mActiveItems.values().iterator(); 483 while (it.hasNext()) { 484 ActiveItem ai = it.next(); 485 if (ai.mRootView.getWindowToken() == null) { 486 // Clean out any dead views, just in case. 487 it.remove(); 488 continue; 489 } 490 ai.updateTime(getContext(), mBuilder); 491 } 492 } 493 494 @Override onRefreshUi(int what)495 public void onRefreshUi(int what) { 496 switch (what) { 497 case REFRESH_TIME: 498 updateTimes(); 499 break; 500 case REFRESH_DATA: 501 refreshUi(false); 502 updateTimes(); 503 break; 504 case REFRESH_STRUCTURE: 505 refreshUi(true); 506 updateTimes(); 507 break; 508 } 509 } 510 } 511