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
17package com.android.launcher3.testing;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Paint;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Message;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.util.TypedValue;
32import android.view.Gravity;
33import android.view.View;
34import android.widget.LinearLayout;
35import android.widget.TextView;
36
37import com.android.launcher3.util.Thunk;
38
39public 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
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
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
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
120    public void onAttachedToWindow() {
121        super.onAttachedToWindow();
122        mHandler.sendEmptyMessage(MSG_START);
123    }
124
125    @Override
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
137        public ProcessWatcher(Context context) {
138            this(context, null);
139        }
140
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
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
178        public int getPid() {
179            return mPid;
180        }
181
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
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
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
239            public GraphView(Context context) {
240                this(context, null);
241            }
242
243            @Override
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