package com.google.android.diskusage.datasource.debug; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Build; import android.os.Environment; import com.google.android.diskusage.datasource.AppStats; import com.google.android.diskusage.datasource.AppStatsCallback; import com.google.android.diskusage.datasource.DataSource; import com.google.android.diskusage.datasource.LegacyFile; import com.google.android.diskusage.datasource.PkgInfo; import com.google.android.diskusage.datasource.PortableFile; import com.google.android.diskusage.datasource.StatFsSource; import com.google.android.diskusage.datasource.debug.PortableStreamProtoWriterImpl.CloseCallback; import com.google.android.diskusage.datasource.fast.DefaultDataSource; import com.google.android.diskusage.datasource.fast.StreamCopy; import com.google.android.diskusage.proto.AppInfoProto; import com.google.android.diskusage.proto.AppStatsProto; import com.google.android.diskusage.proto.Dump; import com.google.android.diskusage.proto.NativeScanProto; import com.google.android.diskusage.proto.PortableFileProto; import com.google.android.diskusage.proto.PortableStreamProto; import com.google.android.diskusage.proto.StatFsProto; import com.google.protobuf.nano.MessageNano; public class DebugDataSource extends DataSource { private final Dump dump; private final DataSource delegate; private DebugDataSource(Dump dump, DataSource delegate) { this.dump = dump; this.delegate = delegate; } private static File dumpFile() { String path = Environment.getExternalStorageDirectory().getPath() + "/diskusage.bin"; File dumpFile = new File(path); return dumpFile; } public static boolean dumpExist() { File dumpFile = dumpFile(); return dumpFile.exists(); } public static DebugDataSource initNewDump(Context c) throws IOException { PackageInfo info; Dump dump = new Dump(); try { info = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); } catch (NameNotFoundException e) { throw new RuntimeException(e); } dump.version = info.versionName; dump.versionInt = info.versionCode; DefaultDataSource delegate = new DefaultDataSource(); dump.isDeviceRooted = delegate.isDeviceRooted(); dump.androidVersion = delegate.getAndroidVersion(); return new DebugDataSource(dump, delegate); } public static DebugDataSource loadDefaultDump() throws IOException { return loadDump(dumpFile()); } public static DebugDataSource loadDump(File dumpFile) throws IOException { Dump dump = Dump.parseFrom(StreamCopy.readFully(dumpFile)); return new DebugDataSource(dump, null); } @Override public int getAndroidVersion() { return dump.androidVersion; } @Override public synchronized List<PkgInfo> getInstalledPackages(PackageManager pm) { if (dump.appInfo == null || dump.appInfo.length == 0) { List<PkgInfo> packages = delegate.getInstalledPackages(pm); dump.appInfo = new AppInfoProto[packages.size()]; int i = 0; for (PkgInfo pkgInfo : packages) { AppInfoProto proto = AppInfoProtoImpl.makeProto(pkgInfo, dump.androidVersion); dump.appInfo[i++] = proto; } } List<PkgInfo> result = new ArrayList<PkgInfo>(); for (int i = 0; i < dump.appInfo.length; i++) { result.add(new AppInfoProtoImpl(dump.appInfo[i], dump.androidVersion)); } return result; } @Override public void getPackageSizeInfo( final PkgInfo pkgInfo, final Method getPackageSizeInfo, final PackageManager pm, final AppStatsCallback callback) throws Exception { AppInfoProtoImpl appInfoImpl = (AppInfoProtoImpl) pkgInfo; final AppInfoProto proto = appInfoImpl.proto; if (proto.stats != null) { AppStatsProto stats = proto.stats; callback.onGetStatsCompleted( stats.hasAppStats ? new AppStatsProtoImpl(stats, dump.androidVersion) : null, stats.succeeded); return; } delegate.getPackageSizeInfo( pkgInfo, getPackageSizeInfo, pm, new AppStatsCallback() { @Override public void onGetStatsCompleted(AppStats appStats, boolean succeeded) { AppStatsProto stats = AppStatsProtoImpl.makeProto( appStats, succeeded, dump.androidVersion); proto.stats = stats; callback.onGetStatsCompleted( stats.hasAppStats ? new AppStatsProtoImpl( stats, dump.androidVersion) : null, stats.succeeded); stats.callbackChildFinished = true; } }); } @Override public synchronized StatFsSource statFs(String mountPoint) { int emptyPos = -1; for (int i = 0; i < dump.statFs.length; i++) { if (dump.statFs[i] == null) { emptyPos = i; } else if (mountPoint.equals(dump.statFs[i].mountPoint)) { return new StatFsSourceProtoImpl(dump.statFs[i], dump.androidVersion); } } if (emptyPos == -1) { StatFsProto[] old = dump.statFs; dump.statFs = new StatFsProto[old.length * 2 + 3]; System.arraycopy(old, 0, dump.statFs, 0, old.length); emptyPos = old.length; } StatFsProto proto = dump.statFs[emptyPos] = StatFsSourceProtoImpl.makeProto( mountPoint, delegate.statFs(mountPoint), dump.androidVersion); return new StatFsSourceProtoImpl(proto, dump.androidVersion); } @TargetApi(Build.VERSION_CODES.FROYO) @Override public PortableFile getExternalFilesDir(Context context) { if (dump.androidVersion < Build.VERSION_CODES.FROYO) { throw new NoClassDefFoundError("Undefined before FROYO"); } if (dump.externalFilesDir == null) { dump.externalFilesDir = PortableFileProtoImpl.makeProto( delegate.getExternalFilesDir(context), dump.androidVersion); } return PortableFileProtoImpl.make(dump.externalFilesDir, dump.androidVersion); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public PortableFile[] getExternalFilesDirs(Context context) { if (dump.androidVersion < Build.VERSION_CODES.KITKAT) { throw new NoClassDefFoundError("Undefined before KITKAT"); } if (dump.externalFilesDirs == null || dump.externalFilesDirs.length == 0) { PortableFile[] externalFilesDirs = delegate.getExternalFilesDirs(context); PortableFileProto[] protos = new PortableFileProto[externalFilesDirs.length]; for (int i = 0; i < protos.length; i++) { protos[i] = PortableFileProtoImpl.makeProto(externalFilesDirs[i], dump.androidVersion); } dump.externalFilesDirs = protos; } PortableFile[] result = new PortableFile[dump.externalFilesDirs.length]; for (int i = 0; i < result.length; i++) { result[i] = PortableFileProtoImpl.make(dump.externalFilesDirs[i], dump.androidVersion); } return result; } @Override public PortableFile getExternalStorageDirectory() { if (dump.externalStorageDirectory == null) { dump.externalStorageDirectory = PortableFileProtoImpl.makeProto( delegate.getExternalStorageDirectory(), dump.androidVersion); } return PortableFileProtoImpl.make(dump.externalStorageDirectory, dump.androidVersion); } @Override public boolean isDeviceRooted() { return dump.isDeviceRooted; } @Override public InputStream createNativeScanner(Context context, String path, boolean rootRequired) throws IOException, InterruptedException { int emptyPos = -1; for (int i = 0; i < dump.nativeScan.length; i++) { if (dump.nativeScan[i] == null) { emptyPos = i; } else if (path.equals(dump.nativeScan[i].path) && rootRequired == dump.nativeScan[i].rootRequired) { return PortableStreamProtoReaderImpl.create(dump.nativeScan[i].stream); } } if (emptyPos == -1) { NativeScanProto[] old = dump.nativeScan; dump.nativeScan = new NativeScanProto[old.length * 2 + 3]; System.arraycopy(old, 0, dump.nativeScan, 0, old.length); emptyPos = old.length; } final NativeScanProto proto = dump.nativeScan[emptyPos] = new NativeScanProto(); proto.path = path; proto.rootRequired = rootRequired; return PortableStreamProtoWriterImpl.create( delegate.createNativeScanner(context, path, rootRequired), new CloseCallback() { @Override public void onClose(PortableStreamProto stream) { proto.stream = stream; } }); } @Override public LegacyFile createLegacyScanFile(String root) { return delegate.createLegacyScanFile(root); } @Override public InputStream getProc() throws IOException { if (dump.proc != null) { return PortableStreamProtoReaderImpl.create(dump.proc); } return PortableStreamProtoWriterImpl.create(delegate.getProc(), new CloseCallback() { @Override public void onClose(PortableStreamProto proto) { dump.proc = proto; } }); } @Override public PortableFile getParentFile(PortableFile in) { PortableFileProtoImpl file = (PortableFileProtoImpl) in; if (file.proto.parent != null) { return PortableFileProtoImpl.make(file.proto.parent, dump.androidVersion); } file.proto.parent = PortableFileProtoImpl.makeProto( delegate.getParentFile(in), dump.androidVersion); return PortableFileProtoImpl.make(file.proto.parent, dump.androidVersion); } public void saveDumpAndSendReport(Context context) throws IOException { byte[] dumpBytes = MessageNano.toByteArray(dump); InputStream is = new ByteArrayInputStream(dumpBytes); File dumpFile = dumpFile(); OutputStream os = new FileOutputStream(dumpFile); StreamCopy.copyStream(is, os); Intent emailIntent = new Intent(Intent.ACTION_SEND); emailIntent.setType("message/rfc822"); emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"ivan.volosyuk+diskusage@gmail.com"}); emailIntent.putExtra(Intent.EXTRA_SUBJECT, "DiskUsage bugreport"); emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(dumpFile)); emailIntent.putExtra(Intent.EXTRA_TEXT, "Please add some description of a problem\n" + "The attached dump may contain file names and install application names\n"); context.startActivity(Intent.createChooser( emailIntent, "Send bugreport email...")); } }