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.app.Activity;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.content.pm.PackageManager;
25import android.net.Uri;
26import android.os.Build;
27import android.os.Bundle;
28import android.os.Environment;
29import android.os.IBinder;
30import android.util.Log;
31
32import java.io.BufferedInputStream;
33import java.io.BufferedOutputStream;
34import java.io.File;
35import java.io.FileInputStream;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.io.InputStream;
39import java.io.OutputStream;
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.zip.ZipEntry;
43import java.util.zip.ZipOutputStream;
44
45public class MemoryDumpActivity extends Activity {
46    private static final String TAG = "MemoryDumpActivity";
47
48    @Override
49    public void onCreate(Bundle savedInstanceState) {
50        super.onCreate(savedInstanceState);
51    }
52
53    public static String zipUp(ArrayList<String> paths) {
54        final int BUFSIZ = 256 * 1024; // 256K
55        final byte[] buf = new byte[BUFSIZ];
56        final String zipfilePath = String.format("%s/hprof-%d.zip",
57                Environment.getExternalStorageDirectory(),
58                System.currentTimeMillis());
59        ZipOutputStream zos = null;
60        try {
61            OutputStream os = new FileOutputStream(zipfilePath);
62            zos = new ZipOutputStream(new BufferedOutputStream(os));
63            for (String filename : paths) {
64                InputStream is = null;
65                try {
66                    is = new BufferedInputStream(new FileInputStream(filename));
67                    ZipEntry entry = new ZipEntry(filename);
68                    zos.putNextEntry(entry);
69                    int len;
70                    while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) {
71                        zos.write(buf, 0, len);
72                    }
73                    zos.closeEntry();
74                } finally {
75                    is.close();
76                }
77            }
78        } catch (IOException e) {
79            Log.e(TAG, "error zipping up profile data", e);
80            return null;
81        } finally {
82            if (zos != null) {
83                try {
84                    zos.close();
85                } catch (IOException e) {
86                    // ugh, whatever
87                }
88            }
89        }
90        return zipfilePath;
91    }
92
93    public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) {
94        final StringBuilder body = new StringBuilder();
95
96        final ArrayList<String> paths = new ArrayList<String>();
97        final int myPid = android.os.Process.myPid();
98
99        final int[] pids_orig = tracker.getTrackedProcesses();
100        final int[] pids_copy = Arrays.copyOf(pids_orig, pids_orig.length);
101        for (int pid : pids_copy) {
102            MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid);
103            if (info != null) {
104                body.append("pid ").append(pid).append(":")
105                    .append(" up=").append(info.getUptime())
106                    .append(" pss=").append(info.currentPss)
107                    .append(" uss=").append(info.currentUss)
108                    .append("\n");
109            }
110            if (pid == myPid) {
111                final String path = String.format("%s/launcher-memory-%d.ahprof",
112                        Environment.getExternalStorageDirectory(),
113                        pid);
114                Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
115                try {
116                    android.os.Debug.dumpHprofData(path); // will block
117                } catch (IOException e) {
118                    Log.e(TAG, "error dumping memory:", e);
119                }
120                paths.add(path);
121            }
122        }
123
124        String zipfile = zipUp(paths);
125
126        if (zipfile == null) return;
127
128        Intent shareIntent = new Intent(Intent.ACTION_SEND);
129        shareIntent.setType("application/zip");
130
131        final PackageManager pm = context.getPackageManager();
132        shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (%d)", myPid));
133        String appVersion;
134        try {
135            appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
136        } catch (PackageManager.NameNotFoundException e) {
137            appVersion = "?";
138        }
139
140        body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n");
141        shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
142
143        final File pathFile = new File(zipfile);
144        final Uri pathUri = Uri.fromFile(pathFile);
145
146        shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
147        context.startActivity(shareIntent);
148    }
149
150    @Override
151    public void onStart() {
152        super.onStart();
153
154        startDump(this, new Runnable() {
155            @Override
156            public void run() {
157                finish();
158            }
159        });
160    }
161
162    public static void startDump(final Context context) {
163        startDump(context, null);
164    }
165
166    public static void startDump(final Context context, final Runnable andThen) {
167        final ServiceConnection connection = new ServiceConnection() {
168            public void onServiceConnected(ComponentName className, IBinder service) {
169                Log.v(TAG, "service connected, dumping...");
170                dumpHprofAndShare(context,
171                        ((MemoryTracker.MemoryTrackerInterface) service).getService());
172                context.unbindService(this);
173                if (andThen != null) andThen.run();
174            }
175
176            public void onServiceDisconnected(ComponentName className) {
177            }
178        };
179        Log.v(TAG, "attempting to bind to memory tracker");
180        context.bindService(new Intent(context, MemoryTracker.class),
181                connection, Context.BIND_AUTO_CREATE);
182    }
183}
184