1 /*
2  * Copyright (C) 2013 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.launcher3.testing;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.Paint;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Message;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.util.TypedValue;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.widget.LinearLayout;
35 import android.widget.TextView;
36 
37 import com.android.launcher3.util.Thunk;
38 
39 public class WeightWatcher extends LinearLayout {
40     private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
41     private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
42     private static final int TEXT_COLOR = 0xFFFFFFFF;
43     private static final int BACKGROUND_COLOR = 0xc0000000;
44 
45     private static final int UPDATE_RATE = 5000;
46 
47     private static final int MSG_START = 1;
48     private static final int MSG_STOP = 2;
49     private static final int MSG_UPDATE = 3;
50 
indexOf(int[] a, int x)51     static int indexOf(int[] a, int x) {
52         for (int i=0; i<a.length; i++) {
53             if (a[i] == x) return i;
54         }
55         return -1;
56     }
57 
58     Handler mHandler = new Handler() {
59         @Override
60         public void handleMessage(Message m) {
61             switch (m.what) {
62                 case MSG_START:
63                     mHandler.sendEmptyMessage(MSG_UPDATE);
64                     break;
65                 case MSG_STOP:
66                     mHandler.removeMessages(MSG_UPDATE);
67                     break;
68                 case MSG_UPDATE:
69                     int[] pids = mMemoryService.getTrackedProcesses();
70 
71                     final int N = getChildCount();
72                     if (pids.length != N) initViews();
73                     else for (int i=0; i<N; i++) {
74                         ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
75                         if (indexOf(pids, pw.getPid()) < 0) {
76                             initViews();
77                             break;
78                         }
79                         pw.update();
80                     }
81                     mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
82                     break;
83             }
84         }
85     };
86     @Thunk MemoryTracker mMemoryService;
87 
WeightWatcher(Context context, AttributeSet attrs)88     public WeightWatcher(Context context, AttributeSet attrs) {
89         super(context, attrs);
90 
91         ServiceConnection connection = new ServiceConnection() {
92             public void onServiceConnected(ComponentName className, IBinder service) {
93                 mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
94                 initViews();
95             }
96 
97             public void onServiceDisconnected(ComponentName className) {
98                 mMemoryService = null;
99             }
100         };
101         context.bindService(new Intent(context, MemoryTracker.class),
102                 connection, Context.BIND_AUTO_CREATE);
103 
104         setOrientation(LinearLayout.VERTICAL);
105 
106         setBackgroundColor(BACKGROUND_COLOR);
107     }
108 
initViews()109     public void initViews() {
110         removeAllViews();
111         int[] processes = mMemoryService.getTrackedProcesses();
112         for (int i=0; i<processes.length; i++) {
113             final ProcessWatcher v = new ProcessWatcher(getContext());
114             v.setPid(processes[i]);
115             addView(v);
116         }
117     }
118 
119     @Override
onAttachedToWindow()120     public void onAttachedToWindow() {
121         super.onAttachedToWindow();
122         mHandler.sendEmptyMessage(MSG_START);
123     }
124 
125     @Override
onDetachedFromWindow()126     public void onDetachedFromWindow() {
127         super.onDetachedFromWindow();
128         mHandler.sendEmptyMessage(MSG_STOP);
129     }
130 
131     public class ProcessWatcher extends LinearLayout {
132         GraphView mRamGraph;
133         TextView mText;
134         int mPid;
135         @Thunk MemoryTracker.ProcessMemInfo mMemInfo;
136 
ProcessWatcher(Context context)137         public ProcessWatcher(Context context) {
138             this(context, null);
139         }
140 
ProcessWatcher(Context context, AttributeSet attrs)141         public ProcessWatcher(Context context, AttributeSet attrs) {
142             super(context, attrs);
143 
144             final float dp = getResources().getDisplayMetrics().density;
145 
146             mText = new TextView(getContext());
147             mText.setTextColor(TEXT_COLOR);
148             mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
149             mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
150 
151             final int p = (int)(2*dp);
152             setPadding(p, 0, p, 0);
153 
154             mRamGraph = new GraphView(getContext());
155 
156             LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
157                     0,
158                     (int)(14 * dp),
159                     1f
160             );
161 
162             addView(mText, params);
163             params.leftMargin = (int)(4*dp);
164             params.weight = 0f;
165             params.width = (int)(200 * dp);
166             addView(mRamGraph, params);
167         }
168 
setPid(int pid)169         public void setPid(int pid) {
170             mPid = pid;
171             mMemInfo = mMemoryService.getMemInfo(mPid);
172             if (mMemInfo == null) {
173                 Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
174                 initViews();
175             }
176         }
177 
getPid()178         public int getPid() {
179             return mPid;
180         }
181 
getUptimeString()182         public String getUptimeString() {
183             long sec = mMemInfo.getUptime() / 1000;
184             StringBuilder sb = new StringBuilder();
185             long days = sec / 86400;
186             if (days > 0) {
187                 sec -= days * 86400;
188                 sb.append(days);
189                 sb.append("d");
190             }
191 
192             long hours = sec / 3600;
193             if (hours > 0) {
194                 sec -= hours * 3600;
195                 sb.append(hours);
196                 sb.append("h");
197             }
198 
199             long mins = sec / 60;
200             if (mins > 0) {
201                 sec -= mins * 60;
202                 sb.append(mins);
203                 sb.append("m");
204             }
205 
206             sb.append(sec);
207             sb.append("s");
208             return sb.toString();
209         }
210 
update()211         public void update() {
212             //Log.v("WeightWatcher.ProcessWatcher",
213             //        "MSG_UPDATE pss=" + mMemInfo.currentPss);
214             mText.setText("(" + mPid
215                           + (mPid == android.os.Process.myPid()
216                                 ? "/A"  // app
217                                 : "/S") // service
218                           + ") up " + getUptimeString()
219                           + " P=" + mMemInfo.currentPss
220                           + " U=" + mMemInfo.currentUss
221                           );
222             mRamGraph.invalidate();
223         }
224 
225         public class GraphView extends View {
226             Paint pssPaint, ussPaint, headPaint;
227 
GraphView(Context context, AttributeSet attrs)228             public GraphView(Context context, AttributeSet attrs) {
229                 super(context, attrs);
230 
231                 pssPaint = new Paint();
232                 pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
233                 ussPaint = new Paint();
234                 ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
235                 headPaint = new Paint();
236                 headPaint.setColor(Color.WHITE);
237             }
238 
GraphView(Context context)239             public GraphView(Context context) {
240                 this(context, null);
241             }
242 
243             @Override
onDraw(Canvas c)244             public void onDraw(Canvas c) {
245                 int w = c.getWidth();
246                 int h = c.getHeight();
247 
248                 if (mMemInfo == null) return;
249 
250                 final int N = mMemInfo.pss.length;
251                 final float barStep = (float) w / N;
252                 final float barWidth = Math.max(1, barStep);
253                 final float scale = (float) h / mMemInfo.max;
254 
255                 int i;
256                 float x;
257                 for (i=0; i<N; i++) {
258                     x = i * barStep;
259                     c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
260                     c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
261                 }
262                 x = mMemInfo.head * barStep;
263                 c.drawRect(x, 0, x + barWidth, h, headPaint);
264             }
265         }
266     }
267 }
268