content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.launcher3.testing;
  
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.ServiceConnection;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Message;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.TypedValue;
  import android.view.Gravity;
  import android.view.View;
  import android.widget.LinearLayout;
  import android.widget.TextView;
  
  import com.android.launcher3.util.Thunk;
  
  public class WeightWatcher extends LinearLayout {
      private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
      private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
      private static final int TEXT_COLOR = 0xFFFFFFFF;
      private static final int BACKGROUND_COLOR = 0xc0000000;
  
      private static final int UPDATE_RATE = 5000;
  
      private static final int MSG_START = 1;
      private static final int MSG_STOP = 2;
      private static final int MSG_UPDATE = 3;
  
      static int indexOf(int[] a, int x) {
          for (int i=0; i<a.length; i++) {
              if (a[i] == x) return i;
          }
          return -1;
      }
  
      Handler mHandler = new Handler() {
          @Override
          public void handleMessage(Message m) {
              switch (m.what) {
                  case MSG_START:
                      mHandler.sendEmptyMessage(MSG_UPDATE);
                      break;
                  case MSG_STOP:
                      mHandler.removeMessages(MSG_UPDATE);
                      break;
                  case MSG_UPDATE:
                      int[] pids = mMemoryService.getTrackedProcesses();
  
                      final int N = getChildCount();
                      if (pids.length != N) initViews();
                      else for (int i=0; i<N; i++) {
                          ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
                          if (indexOf(pids, pw.getPid()) < 0) {
                              initViews();
                              break;
                          }
                          pw.update();
                      }
                      mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
                      break;
              }
          }
      };
      @Thunk MemoryTracker mMemoryService;
  
      public WeightWatcher(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          ServiceConnection connection = new ServiceConnection() {
              public void onServiceConnected(ComponentName className, IBinder service) {
                  mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
                  initViews();
              }
  
              public void onServiceDisconnected(ComponentName className) {
                  mMemoryService = null;
              }
          };
          context.bindService(new Intent(context, MemoryTracker.class),
                  connection, Context.BIND_AUTO_CREATE);
  
          setOrientation(LinearLayout.VERTICAL);
  
          setBackgroundColor(BACKGROUND_COLOR);
      }
  
      public void initViews() {
          removeAllViews();
          int[] processes = mMemoryService.getTrackedProcesses();
          for (int i=0; i<processes.length; i++) {
              final ProcessWatcher v = new ProcessWatcher(getContext());
              v.setPid(processes[i]);
              addView(v);
          }
      }
  
      @Override
      public void onAttachedToWindow() {
          super.onAttachedToWindow();
          mHandler.sendEmptyMessage(MSG_START);
      }
  
      @Override
      public void onDetachedFromWindow() {
          super.onDetachedFromWindow();
          mHandler.sendEmptyMessage(MSG_STOP);
      }
  
      public class ProcessWatcher extends LinearLayout {
          GraphView mRamGraph;
          TextView mText;
          int mPid;
          @Thunk MemoryTracker.ProcessMemInfo mMemInfo;
  
          public ProcessWatcher(Context context) {
              this(context, null);
          }
  
          public ProcessWatcher(Context context, AttributeSet attrs) {
              super(context, attrs);
  
              final float dp = getResources().getDisplayMetrics().density;
  
              mText = new TextView(getContext());
              mText.setTextColor(TEXT_COLOR);
              mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
              mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
  
              final int p = (int)(2*dp);
              setPadding(p, 0, p, 0);
  
              mRamGraph = new GraphView(getContext());
  
              LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                      0,
                      (int)(14 * dp),
                      1f
              );
  
              addView(mText, params);
              params.leftMargin = (int)(4*dp);
              params.weight = 0f;
              params.width = (int)(200 * dp);
              addView(mRamGraph, params);
          }
  
          public void setPid(int pid) {
              mPid = pid;
              mMemInfo = mMemoryService.getMemInfo(mPid);
              if (mMemInfo == null) {
                  Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
                  initViews();
              }
          }
  
          public int getPid() {
              return mPid;
          }
  
          public String getUptimeString() {
              long sec = mMemInfo.getUptime() / 1000;
              StringBuilder sb = new StringBuilder();
              long days = sec / 86400;
              if (days > 0) {
                  sec -= days * 86400;
                  sb.append(days);
                  sb.append("d");
              }
  
              long hours = sec / 3600;
              if (hours > 0) {
                  sec -= hours * 3600;
                  sb.append(hours);
                  sb.append("h");
              }
  
              long mins = sec / 60;
              if (mins > 0) {
                  sec -= mins * 60;
                  sb.append(mins);
                  sb.append("m");
              }
  
              sb.append(sec);
              sb.append("s");
              return sb.toString();
          }
  
          public void update() {
              //Log.v("WeightWatcher.ProcessWatcher",
              //        "MSG_UPDATE pss=" + mMemInfo.currentPss);
              mText.setText("(" + mPid
                            + (mPid == android.os.Process.myPid()
                                  ? "/A"  // app
                                  : "/S") // service
                            + ") up " + getUptimeString()
                            + " P=" + mMemInfo.currentPss
                            + " U=" + mMemInfo.currentUss
                            );
              mRamGraph.invalidate();
          }
  
          public class GraphView extends View {
              Paint pssPaint, ussPaint, headPaint;
  
              public GraphView(Context context, AttributeSet attrs) {
                  super(context, attrs);
  
                  pssPaint = new Paint();
                  pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
                  ussPaint = new Paint();
                  ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
                  headPaint = new Paint();
                  headPaint.setColor(Color.WHITE);
              }
  
              public GraphView(Context context) {
                  this(context, null);
              }
  
              @Override
              public void onDraw(Canvas c) {
                  int w = c.getWidth();
                  int h = c.getHeight();
  
                  if (mMemInfo == null) return;
  
                  final int N = mMemInfo.pss.length;
                  final float barStep = (float) w / N;
                  final float barWidth = Math.max(1, barStep);
                  final float scale = (float) h / mMemInfo.max;
  
                  int i;
                  float x;
                  for (i=0; i<N; i++) {
                      x = i * barStep;
                      c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
                      c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
                  }
                  x = mMemInfo.head * barStep;
                  c.drawRect(x, 0, x + barWidth, h, headPaint);
              }
          }
      }
  }
  