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