diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2015-09-21 20:35:29 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-09-21 20:35:29 +0000 |
commit | e04febbf8ec8326edb095d023e494911381cee66 (patch) | |
tree | 5ac674f93f7128f414c4537400c8ef898cbd40a3 /src/com/android/launcher3/testing | |
parent | 3c9c7263dcd06cba449597104db039a4019e15ff (diff) | |
parent | 322d55622031985c75f7e5db07964b7730a97dac (diff) | |
download | android_packages_apps_Trebuchet-e04febbf8ec8326edb095d023e494911381cee66.tar.gz android_packages_apps_Trebuchet-e04febbf8ec8326edb095d023e494911381cee66.tar.bz2 android_packages_apps_Trebuchet-e04febbf8ec8326edb095d023e494911381cee66.zip |
Merge "Moving a few testing classes to a separate package" into ub-launcher3-master
Diffstat (limited to 'src/com/android/launcher3/testing')
5 files changed, 752 insertions, 0 deletions
diff --git a/src/com/android/launcher3/testing/DummyWidget.java b/src/com/android/launcher3/testing/DummyWidget.java new file mode 100644 index 000000000..df887ac1f --- /dev/null +++ b/src/com/android/launcher3/testing/DummyWidget.java @@ -0,0 +1,53 @@ +package com.android.launcher3.testing; + +import android.appwidget.AppWidgetProviderInfo; + +import com.android.launcher3.CustomAppWidget; +import com.android.launcher3.R; + +public class DummyWidget implements CustomAppWidget { + @Override + public String getLabel() { + return "Dumb Launcher Widget"; + } + + @Override + public int getPreviewImage() { + return 0; + } + + @Override + public int getIcon() { + return 0; + } + + @Override + public int getWidgetLayout() { + return R.layout.zzz_dummy_widget; + } + + @Override + public int getSpanX() { + return 2; + } + + @Override + public int getSpanY() { + return 2; + } + + @Override + public int getMinSpanX() { + return 1; + } + + @Override + public int getMinSpanY() { + return 1; + } + + @Override + public int getResizeMode() { + return AppWidgetProviderInfo.RESIZE_BOTH; + } +} diff --git a/src/com/android/launcher3/testing/MemoryDumpActivity.java b/src/com/android/launcher3/testing/MemoryDumpActivity.java new file mode 100644 index 000000000..9bcf92b1b --- /dev/null +++ b/src/com/android/launcher3/testing/MemoryDumpActivity.java @@ -0,0 +1,183 @@ +/* + * 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.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class MemoryDumpActivity extends Activity { + private static final String TAG = "MemoryDumpActivity"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + public static String zipUp(ArrayList<String> paths) { + final int BUFSIZ = 256 * 1024; // 256K + final byte[] buf = new byte[BUFSIZ]; + final String zipfilePath = String.format("%s/hprof-%d.zip", + Environment.getExternalStorageDirectory(), + System.currentTimeMillis()); + ZipOutputStream zos = null; + try { + OutputStream os = new FileOutputStream(zipfilePath); + zos = new ZipOutputStream(new BufferedOutputStream(os)); + for (String filename : paths) { + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(filename)); + ZipEntry entry = new ZipEntry(filename); + zos.putNextEntry(entry); + int len; + while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) { + zos.write(buf, 0, len); + } + zos.closeEntry(); + } finally { + is.close(); + } + } + } catch (IOException e) { + Log.e(TAG, "error zipping up profile data", e); + return null; + } finally { + if (zos != null) { + try { + zos.close(); + } catch (IOException e) { + // ugh, whatever + } + } + } + return zipfilePath; + } + + public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) { + final StringBuilder body = new StringBuilder(); + + final ArrayList<String> paths = new ArrayList<String>(); + final int myPid = android.os.Process.myPid(); + + final int[] pids_orig = tracker.getTrackedProcesses(); + final int[] pids_copy = Arrays.copyOf(pids_orig, pids_orig.length); + for (int pid : pids_copy) { + MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid); + if (info != null) { + body.append("pid ").append(pid).append(":") + .append(" up=").append(info.getUptime()) + .append(" pss=").append(info.currentPss) + .append(" uss=").append(info.currentUss) + .append("\n"); + } + if (pid == myPid) { + final String path = String.format("%s/launcher-memory-%d.ahprof", + Environment.getExternalStorageDirectory(), + pid); + Log.v(TAG, "Dumping memory info for process " + pid + " to " + path); + try { + android.os.Debug.dumpHprofData(path); // will block + } catch (IOException e) { + Log.e(TAG, "error dumping memory:", e); + } + paths.add(path); + } + } + + String zipfile = zipUp(paths); + + if (zipfile == null) return; + + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("application/zip"); + + final PackageManager pm = context.getPackageManager(); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (%d)", myPid)); + String appVersion; + try { + appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + appVersion = "?"; + } + + body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n"); + shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString()); + + final File pathFile = new File(zipfile); + final Uri pathUri = Uri.fromFile(pathFile); + + shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri); + context.startActivity(shareIntent); + } + + @Override + public void onStart() { + super.onStart(); + + startDump(this, new Runnable() { + @Override + public void run() { + finish(); + } + }); + } + + public static void startDump(final Context context) { + startDump(context, null); + } + + public static void startDump(final Context context, final Runnable andThen) { + final ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.v(TAG, "service connected, dumping..."); + dumpHprofAndShare(context, + ((MemoryTracker.MemoryTrackerInterface) service).getService()); + context.unbindService(this); + if (andThen != null) andThen.run(); + } + + public void onServiceDisconnected(ComponentName className) { + } + }; + Log.v(TAG, "attempting to bind to memory tracker"); + context.bindService(new Intent(context, MemoryTracker.class), + connection, Context.BIND_AUTO_CREATE); + } +} diff --git a/src/com/android/launcher3/testing/MemoryTracker.java b/src/com/android/launcher3/testing/MemoryTracker.java new file mode 100644 index 000000000..ed2a3122c --- /dev/null +++ b/src/com/android/launcher3/testing/MemoryTracker.java @@ -0,0 +1,217 @@ +/* + * 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.app.ActivityManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; +import android.util.LongSparseArray; + +import com.android.launcher3.util.TestingUtils; + +import java.util.ArrayList; +import java.util.List; + +public class MemoryTracker extends Service { + public static final String TAG = MemoryTracker.class.getSimpleName(); + + private static final long UPDATE_RATE = 5000; + + private static final int MSG_START = 1; + private static final int MSG_STOP = 2; + private static final int MSG_UPDATE = 3; + + public static class ProcessMemInfo { + public int pid; + public String name; + public long startTime; + public long currentPss, currentUss; + public long[] pss = new long[256]; + public long[] uss = new long[256]; + //= new Meminfo[(int) (30 * 60 / (UPDATE_RATE / 1000))]; // 30 minutes + public long max = 1; + public int head = 0; + public ProcessMemInfo(int pid, String name, long start) { + this.pid = pid; + this.name = name; + this.startTime = start; + } + public long getUptime() { + return System.currentTimeMillis() - startTime; + } + }; + public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>(); + public final ArrayList<Long> mPids = new ArrayList<Long>(); + private int[] mPidsArray = new int[0]; + private final Object mLock = new Object(); + + Handler mHandler = new Handler() { + @Override + public void handleMessage(Message m) { + switch (m.what) { + case MSG_START: + mHandler.removeMessages(MSG_UPDATE); + mHandler.sendEmptyMessage(MSG_UPDATE); + break; + case MSG_STOP: + mHandler.removeMessages(MSG_UPDATE); + break; + case MSG_UPDATE: + update(); + mHandler.removeMessages(MSG_UPDATE); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE); + break; + } + } + }; + + ActivityManager mAm; + + public ProcessMemInfo getMemInfo(int pid) { + return mData.get(pid); + } + + public int[] getTrackedProcesses() { + return mPidsArray; + } + + public void startTrackingProcess(int pid, String name, long start) { + synchronized (mLock) { + final Long lpid = Long.valueOf(pid); + + if (mPids.contains(lpid)) return; + + mPids.add(lpid); + updatePidsArrayL(); + + mData.put(pid, new ProcessMemInfo(pid, name, start)); + } + } + + void updatePidsArrayL() { + final int N = mPids.size(); + mPidsArray = new int[N]; + StringBuffer sb = new StringBuffer("Now tracking processes: "); + for (int i=0; i<N; i++) { + final int p = mPids.get(i).intValue(); + mPidsArray[i] = p; + sb.append(p); sb.append(" "); + } + Log.v(TAG, sb.toString()); + } + + void update() { + synchronized (mLock) { + Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray); + for (int i=0; i<dinfos.length; i++) { + Debug.MemoryInfo dinfo = dinfos[i]; + if (i > mPids.size()) { + Log.e(TAG, "update: unknown process info received: " + dinfo); + break; + } + final long pid = mPids.get(i).intValue(); + final ProcessMemInfo info = mData.get(pid); + info.head = (info.head+1) % info.pss.length; + info.pss[info.head] = info.currentPss = dinfo.getTotalPss(); + info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty(); + if (info.currentPss > info.max) info.max = info.currentPss; + if (info.currentUss > info.max) info.max = info.currentUss; + // Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss); + if (info.currentPss == 0) { + Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died"); + mData.remove(pid); + } + } + for (int i=mPids.size()-1; i>=0; i--) { + final long pid = mPids.get(i).intValue(); + if (mData.get(pid) == null) { + mPids.remove(i); + updatePidsArrayL(); + } + } + } + } + + @Override + public void onCreate() { + mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + + // catch up in case we crashed but other processes are still running + List<ActivityManager.RunningServiceInfo> svcs = mAm.getRunningServices(256); + for (ActivityManager.RunningServiceInfo svc : svcs) { + if (svc.service.getPackageName().equals(getPackageName())) { + Log.v(TAG, "discovered running service: " + svc.process + " (" + svc.pid + ")"); + startTrackingProcess(svc.pid, svc.process, + System.currentTimeMillis() - (SystemClock.elapsedRealtime() - svc.activeSince)); + } + } + + List<ActivityManager.RunningAppProcessInfo> procs = mAm.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo proc : procs) { + final String pname = proc.processName; + if (pname.startsWith(getPackageName())) { + Log.v(TAG, "discovered other running process: " + pname + " (" + proc.pid + ")"); + startTrackingProcess(proc.pid, pname, System.currentTimeMillis()); + } + } + } + + @Override + public void onDestroy() { + mHandler.sendEmptyMessage(MSG_STOP); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.v(TAG, "Received start id " + startId + ": " + intent); + + if (intent != null) { + if (TestingUtils.ACTION_START_TRACKING.equals(intent.getAction())) { + final int pid = intent.getIntExtra("pid", -1); + final String name = intent.getStringExtra("name"); + final long start = intent.getLongExtra("start", System.currentTimeMillis()); + startTrackingProcess(pid, name, start); + } + } + + mHandler.sendEmptyMessage(MSG_START); + + return START_STICKY; + } + + public class MemoryTrackerInterface extends Binder { + MemoryTracker getService() { + return MemoryTracker.this; + } + } + + private final IBinder mBinder = new MemoryTrackerInterface(); + + public IBinder onBind(Intent intent) { + mHandler.sendEmptyMessage(MSG_START); + + return mBinder; + } +} diff --git a/src/com/android/launcher3/testing/ToggleWeightWatcher.java b/src/com/android/launcher3/testing/ToggleWeightWatcher.java new file mode 100644 index 000000000..15e55eea6 --- /dev/null +++ b/src/com/android/launcher3/testing/ToggleWeightWatcher.java @@ -0,0 +1,32 @@ +package com.android.launcher3.testing; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.View; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.util.TestingUtils; + +public class ToggleWeightWatcher extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String spKey = LauncherAppState.getSharedPreferencesKey(); + SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); + boolean show = sp.getBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, true); + + show = !show; + sp.edit().putBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, show).apply(); + + Launcher launcher = (Launcher) LauncherAppState.getInstance().getModel().getCallback(); + if (launcher != null && launcher.mWeightWatcher != null) { + launcher.mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE); + } + finish(); + } +} diff --git a/src/com/android/launcher3/testing/WeightWatcher.java b/src/com/android/launcher3/testing/WeightWatcher.java new file mode 100644 index 000000000..a26a2b642 --- /dev/null +++ b/src/com/android/launcher3/testing/WeightWatcher.java @@ -0,0 +1,267 @@ +/* + * 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); + } + } + } +} |