tocReferences = getBook().getTableOfContents().getTocReferences();
74 | for (int i = 0; i < tocReferences.size(); i++) {
75 | TOCReference tocReference = tocReferences.get(i);
76 | int spinePositionForTocReference = getSpinePositionForTocReference(tocReference);
77 | if (spinePositionForTocReference == spinePosition) {
78 | return i;
79 | }
80 | if (spinePositionForTocReference > spinePosition) {
81 | return i - 1;
82 | }
83 | }
84 | return -1;
85 | }
86 |
87 | public File getOpfPath() {
88 | if (opfPath != null) {
89 | return opfPath;
90 | }
91 | opfPath = EpubStorageHelper.getOpfPath(this);
92 | return opfPath;
93 | }
94 |
95 | public File getLocation() {
96 | return location;
97 | }
98 |
99 | /**
100 | * returns the file input stream of a resources located in an epub
101 | * in difference to {@link Resource#getData()} the stream is not cached in memory
102 | */
103 | public InputStream getResourceContent(Resource resource) throws FileNotFoundException {
104 | File file = new File(getOpfPath(), resource.getHref());
105 | return new FileInputStream(file);
106 | }
107 |
108 | /**
109 | * Accepts the following URI schemes:
110 | *
111 | * - content
112 | * - android.resource
113 | * - file
114 | * - file/android_assets
115 | *
116 | *
117 | * @param context
118 | * @param uri
119 | * @throws IOException
120 | */
121 | @WorkerThread
122 | public static Epub fromUri(Context context, String uri) throws IOException {
123 | return EpubStorageHelper.fromUri(context, uri);
124 | }
125 |
126 | /**
127 | * @param context
128 | * @param folder uncompressed epub directory
129 | * @throws IOException
130 | */
131 | @WorkerThread
132 | public static Epub fromFolder(Context context, File folder) throws IOException {
133 | return EpubStorageHelper.fromFolder(context, folder);
134 | }
135 |
136 | /**
137 | * removes all cached extracted data for this epub
138 | * the original epub file will not be removed
139 | *
140 | * !!! it is not usable after calling this function !!!
141 | * make sure the epub is not currently displayed in any {@link EpubView}
142 | *
143 | * you need to recreate the epub with {@link #fromUri(Context, String)} again
144 | */
145 | @WorkerThread
146 | public void destroy() throws IOException {
147 | FileUtils.deleteDirectory(getLocation());
148 | }
149 |
150 |
151 | /**
152 | * @see #destroy()
153 | * destroys the cache for an epub without the need of creating an {@link Epub} instance before
154 | *
155 | * @param uri epub uri
156 | */
157 | @WorkerThread
158 | public static void destroyForUri(Context context, String uri) throws IOException {
159 | FileUtils.deleteDirectory(Unzipper.getEpubCacheFolder(EpubStorageHelper.getEpubReaderCacheDir(context), uri));
160 | }
161 |
162 | /**
163 | * checks if this epub is destroyed
164 | */
165 | public boolean isDestroyed() {
166 | return !getLocation().exists();
167 | }
168 |
169 | @Override
170 | public boolean equals(Object o) {
171 | if (this == o) return true;
172 | if (!(o instanceof Epub)) return false;
173 |
174 | Epub epub = (Epub) o;
175 |
176 | if (opfPath != null ? !opfPath.equals(epub.opfPath) : epub.opfPath != null) {
177 | return false;
178 | }
179 | if (location != null ? !location.equals(epub.location) : epub.location != null) {
180 | return false;
181 | }
182 | return book != null ? book.equals(epub.book) : epub.book == null;
183 |
184 | }
185 |
186 | @Override
187 | public int hashCode() {
188 | int result = opfPath != null ? opfPath.hashCode() : 0;
189 | result = 31 * result + (location != null ? location.hashCode() : 0);
190 | result = 31 * result + (book != null ? book.hashCode() : 0);
191 | return result;
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/EpubFont.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | import com.google.auto.value.AutoValue;
6 |
7 | @AutoValue
8 | public abstract class EpubFont {
9 |
10 | @Nullable
11 | public abstract String name();
12 |
13 | @Nullable
14 | public abstract String uri();
15 |
16 | public static EpubFont fromUri(String name, String uri) {
17 | return new AutoValue_EpubFont(name, uri);
18 | }
19 |
20 | public static EpubFont fromFontFamily(String fontFamily) {
21 | return new AutoValue_EpubFont(fontFamily, null);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/EpubLocation.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import android.os.Parcelable;
4 | import android.support.annotation.NonNull;
5 |
6 | import com.google.auto.value.AutoValue;
7 |
8 | public abstract class EpubLocation implements Parcelable {
9 |
10 | EpubLocation() {
11 | }
12 |
13 | public static EpubLocation fromID(@NonNull int chapter, @NonNull String id) {
14 | return new AutoValue_EpubLocation_IdLocation(chapter, id);
15 | }
16 |
17 | public static EpubLocation fromRange(@NonNull int chapter, int start, int end) {
18 | return new AutoValue_EpubLocation_RangeLocation(chapter, start, end);
19 | }
20 |
21 | public static ChapterLocation fromChapter(@NonNull int chapter) {
22 | return new AutoValue_EpubLocation_ChapterLocationImpl(chapter);
23 | }
24 |
25 | public static XPathLocation fromXPath(int chapter, String xPath) {
26 | return new AutoValue_EpubLocation_XPathLocation(chapter, xPath);
27 | }
28 |
29 | public abstract static class ChapterLocation extends EpubLocation {
30 | public abstract int chapter();
31 | }
32 |
33 | @AutoValue
34 | public abstract static class ChapterLocationImpl extends ChapterLocation {
35 | }
36 |
37 | @AutoValue
38 | public abstract static class IdLocation extends ChapterLocation {
39 | public abstract String id();
40 | }
41 |
42 | @AutoValue
43 | public abstract static class XPathLocation extends ChapterLocation {
44 | public abstract String xPath();
45 | }
46 |
47 | @AutoValue
48 | public abstract static class RangeLocation extends ChapterLocation {
49 | public abstract int start();
50 |
51 | public abstract int end();
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/EpubStorageHelper.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 |
13 | import nl.siegmann.epublib.domain.Book;
14 |
15 | class EpubStorageHelper {
16 |
17 | static final String ANDROID_ASSETS = "file:///android_asset/";
18 |
19 | static File getEpubReaderCacheDir(Context context) {
20 | File cacheDir = new File(context.getCacheDir(), "epubreader_cache");
21 | cacheDir.mkdirs();
22 | return cacheDir;
23 | }
24 |
25 | static File getOpfPath(Epub epub) {
26 | return getOpfPath(epub.getLocation());
27 | }
28 |
29 | static File getOpfPath(File folder) {
30 | String relativeOpfPath = "";
31 | try {
32 | // get the OPF path, directly from container.xml
33 |
34 | BufferedReader br
35 | = new BufferedReader(new InputStreamReader(new FileInputStream(folder
36 | + "/META-INF/container.xml"), "UTF-8"));
37 |
38 | String line;
39 | while ((line = br.readLine()) != null) {
40 | //if (line.indexOf(getS(R.string.full_path)) > -1)
41 | if (line.contains("full-path")) {
42 | int start = line.indexOf("full-path");
43 | //int start2 = line.indexOf("\"", start);
44 | int start2 = line.indexOf('\"', start);
45 | int stop2 = line.indexOf('\"', start2 + 1);
46 | if (start2 > -1 && stop2 > start2) {
47 | relativeOpfPath = line.substring(start2 + 1, stop2).trim();
48 | break;
49 | }
50 | }
51 | }
52 | br.close();
53 |
54 | // in case the OPF file is in the root directory
55 | if (!relativeOpfPath.contains("/")) {
56 | return folder;
57 | }
58 |
59 | // remove the OPF file name and the preceding '/'
60 | int last = relativeOpfPath.lastIndexOf('/');
61 | if (last > -1) {
62 | relativeOpfPath = relativeOpfPath.substring(0, last);
63 | }
64 |
65 | return new File(folder, relativeOpfPath);
66 | } catch (NullPointerException | IOException e) {
67 | e.printStackTrace();
68 | }
69 | return folder;
70 | }
71 |
72 | static Epub fromUri(Context context, String uri) throws IOException {
73 | File cacheDir = getEpubReaderCacheDir(context);
74 | File unzippedEpubLocation = Unzipper.unzipEpubIfNeeded(context, uri, cacheDir);
75 | try {
76 | Book book = UncompressedEpubReader.readUncompressedBook(unzippedEpubLocation);
77 | return new Epub(book, unzippedEpubLocation);
78 | } finally {
79 | System.gc();
80 | }
81 | }
82 |
83 | static Epub fromFolder(Context context, File folder) throws IOException {
84 | try {
85 | Book book = UncompressedEpubReader.readUncompressedBook(folder);
86 | return new Epub(book, folder);
87 | } finally {
88 | System.gc();
89 | }
90 | }
91 |
92 | static InputStream openFromUri(Context context, String uriString) throws IOException {
93 |
94 | Uri uri = Uri.parse(uriString);
95 | InputStream inputStream;
96 | if (uriString.startsWith(ANDROID_ASSETS)) {
97 | inputStream = context.getAssets().open(uriString.replace(ANDROID_ASSETS, ""));
98 | } else {
99 | inputStream = context.getContentResolver().openInputStream(uri);
100 | }
101 | return inputStream;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import java.io.File;
4 | import java.io.FileNotFoundException;
5 | import java.io.IOException;
6 |
7 | /**
8 | * http://grepcode.com/file_/repo1.maven.org/maven2/commons-io/commons-io/1.1/org/apache/commons/io/FileUtils.java/?v=source
9 | */
10 | class FileUtils {
11 |
12 | public static void tryDeleteDirectory(File directory)
13 | throws IOException {
14 | try {
15 | deleteDirectory(directory);
16 | } catch (Exception e){}
17 | }
18 |
19 | /**
20 | * Recursively delete a directory.
21 | * @param directory directory to delete
22 | * @throws IOException in case deletion is unsuccessful
23 | */
24 | public static void deleteDirectory(File directory)
25 | throws IOException {
26 | if (!directory.exists()) {
27 | return;
28 | }
29 |
30 | cleanDirectory(directory);
31 | if (!directory.delete()) {
32 | String message =
33 | "Unable to delete directory " + directory + ".";
34 | throw new IOException(message);
35 | }
36 | }
37 |
38 | /**
39 | * Clean a directory without deleting it.
40 | * @param directory directory to clean
41 | * @throws IOException in case cleaning is unsuccessful
42 | */
43 | public static void cleanDirectory(File directory) throws IOException {
44 | if (!directory.exists()) {
45 | String message = directory + " does not exist";
46 | throw new IllegalArgumentException(message);
47 | }
48 |
49 | if (!directory.isDirectory()) {
50 | String message = directory + " is not a directory";
51 | throw new IllegalArgumentException(message);
52 | }
53 |
54 | File[] files = directory.listFiles();
55 | if (files == null) { // null if security restricted
56 | throw new IOException("Failed to list contents of " + directory);
57 | }
58 |
59 | IOException exception = null;
60 | for (int i = 0; i < files.length; i++) {
61 | File file = files[i];
62 | try {
63 | forceDelete(file);
64 | } catch (IOException ioe) {
65 | exception = ioe;
66 | }
67 | }
68 |
69 | if (null != exception) {
70 | throw exception;
71 | }
72 | }
73 |
74 | /**
75 | *
76 | * Delete a file. If file is a directory, delete it and all sub-directories.
77 | *
78 | *
79 | * The difference between File.delete() and this method are:
80 | *
81 | *
82 | * - A directory to be deleted does not have to be empty.
83 | * - You get exceptions when a file or directory cannot be deleted.
84 | * (java.io.File methods returns a boolean)
85 | *
86 | * @param file file or directory to delete.
87 | * @throws IOException in case deletion is unsuccessful
88 | */
89 | public static void forceDelete(File file) throws IOException {
90 | if (file.isDirectory()) {
91 | deleteDirectory(file);
92 | } else {
93 | if (!file.exists()) {
94 | throw new FileNotFoundException("File does not exist: " + file);
95 | }
96 | if (!file.delete()) {
97 | String message =
98 | "Unable to delete file: " + file;
99 | throw new IOException(message);
100 | }
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/LazyResource.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 |
8 | import nl.siegmann.epublib.domain.Resource;
9 | import nl.siegmann.epublib.util.IOUtil;
10 |
11 | class LazyResource extends Resource {
12 |
13 | private String fileName;
14 |
15 | LazyResource(String fileName, long size, String href) {
16 | super(fileName, size, href);
17 | this.fileName = fileName;
18 | }
19 |
20 | @Override
21 | public byte[] getData() throws IOException {
22 | InputStream in = new BufferedInputStream(new FileInputStream(fileName));
23 | try {
24 | return IOUtil.toByteArray(in);
25 | } finally {
26 | in.close();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/UncompressedEpubReader.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 |
6 | import nl.siegmann.epublib.domain.Book;
7 | import nl.siegmann.epublib.domain.Resource;
8 | import nl.siegmann.epublib.domain.Resources;
9 | import nl.siegmann.epublib.epub.EpubReader;
10 |
11 | class UncompressedEpubReader {
12 |
13 | static Book readUncompressedBook(File folder) throws IOException {
14 | Resources resources = readLazyResources(folder);
15 | return new EpubReader().readEpub(resources);
16 | }
17 |
18 | private static Resources readLazyResources(File folder) throws IOException {
19 | Resources result = new Resources();
20 | readLazyResources(folder, result, folder);
21 | return result;
22 | }
23 |
24 | private static void readLazyResources(File root, Resources resources, File folder) throws IOException {
25 | String hrefRoot = root.getAbsolutePath() + "/";
26 | for (File file : folder.listFiles()) {
27 | if (file.isDirectory()) {
28 | readLazyResources(root, resources, file);
29 | continue;
30 | }
31 | if (file.getName().equals(".ready")) {
32 | continue;
33 | }
34 |
35 | String path = file.getAbsolutePath();
36 | String href = path.replace(hrefRoot, "");
37 | Resource resource = new LazyResource(path, 0, href);
38 | resources.add(resource);
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/model/Unzipper.java:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.model;
2 |
3 | import android.content.Context;
4 |
5 | import java.io.Closeable;
6 | import java.io.File;
7 | import java.io.FileOutputStream;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.OutputStream;
11 | import java.security.MessageDigest;
12 | import java.security.NoSuchAlgorithmException;
13 | import java.util.zip.ZipEntry;
14 | import java.util.zip.ZipInputStream;
15 |
16 | class Unzipper {
17 |
18 | static File getEpubCacheFolder(File destDir, String uri) {
19 | return new File(destDir, md5(uri));
20 | }
21 |
22 | /**
23 | * @return (unzipped location, epubfile)
24 | * @throws IOException
25 | */
26 | static File unzipEpubIfNeeded(Context context, String uri, File destDir) throws IOException {
27 | InputStream inputStream = EpubStorageHelper.openFromUri(context, uri);
28 | File destination = getEpubCacheFolder(destDir, uri);
29 |
30 | if (destination.exists()) {
31 | File ready = new File(destination, ".ready");
32 | if (ready.exists()) {
33 | return destination;
34 | }
35 | }
36 |
37 | try {
38 | unzip(inputStream, destination);
39 | } catch (IOException e) {
40 | //noinspection ResultOfMethodCallIgnored
41 | try {
42 | FileUtils.deleteDirectory(destination);
43 | } catch (Exception ignore) {}
44 |
45 | throw e;
46 | }
47 |
48 | return destination;
49 | }
50 |
51 | private static File copyAsset(Context context, String uri, File destDir) throws IOException {
52 | File file = new File(uri);
53 | File localFile = new File(destDir, file.getName());
54 | if (localFile.exists()) {
55 | return localFile;
56 | }
57 | localFile.createNewFile();
58 | try {
59 | InputStream steam = context.getAssets().open(uri.replace("file:///android_asset/", ""));
60 | copyFile(steam, new FileOutputStream(localFile));
61 | return localFile;
62 | } catch (IOException e) {
63 | e.printStackTrace();
64 | localFile.delete();
65 | }
66 | return file;
67 | }
68 |
69 | private static void copyFile(InputStream in, OutputStream out) throws IOException {
70 | byte[] buffer = new byte[1024];
71 | int read;
72 | while ((read = in.read(buffer)) != -1) {
73 | out.write(buffer, 0, read);
74 | }
75 | out.close();
76 | }
77 |
78 | private static void unzip(InputStream inputStream, File folder) throws IOException {
79 |
80 | if (!folder.exists()) {
81 | folder.mkdirs();
82 | } else {
83 | FileUtils.deleteDirectory(folder);
84 | }
85 |
86 | ZipInputStream zis;
87 |
88 | byte[] buffer = new byte[2048];
89 |
90 | try {
91 | String filename;
92 | zis = new ZipInputStream(inputStream);
93 |
94 | ZipEntry ze;
95 | int count;
96 | while ((ze = zis.getNextEntry()) != null) {
97 | filename = ze.getName();
98 | File file = new File(folder, filename);
99 |
100 | // make directory if necessary
101 | new File(file.getParent()).mkdirs();
102 |
103 | if (!ze.isDirectory() && !file.isDirectory()) {
104 | FileOutputStream fout = new FileOutputStream(file);
105 | while ((count = zis.read(buffer)) != -1) {
106 | fout.write(buffer, 0, count);
107 | }
108 | fout.close();
109 | }
110 | zis.closeEntry();
111 | }
112 |
113 | inputStream.close();
114 | zis.close();
115 |
116 | //file to show that everything is fully unzipped
117 | File ready = new File(folder, ".ready");
118 | if (!ready.exists()) {
119 | ready.createNewFile();
120 | }
121 |
122 | } catch (IOException e) {
123 | FileUtils.tryDeleteDirectory(folder);
124 | throw e;
125 | }
126 | }
127 |
128 | private static void createDir(File dir) {
129 | if (dir.exists()) {
130 | return;
131 | }
132 | if (!dir.mkdirs()) {
133 | throw new RuntimeException("Can not create dir " + dir);
134 | }
135 | }
136 |
137 | private static String md5(final String s) {
138 | final String MD5 = "MD5";
139 | try {
140 | // Create MD5 Hash
141 | MessageDigest digest = java.security.MessageDigest
142 | .getInstance(MD5);
143 | digest.update(s.getBytes());
144 | byte messageDigest[] = digest.digest();
145 |
146 | // Create Hex String
147 | StringBuilder hexString = new StringBuilder();
148 | for (byte aMessageDigest : messageDigest) {
149 | String h = Integer.toHexString(0xFF & aMessageDigest);
150 | while (h.length() < 2)
151 | h = "0" + h;
152 | hexString.append(h);
153 | }
154 | return hexString.toString();
155 |
156 | } catch (NoSuchAlgorithmException e) {
157 | e.printStackTrace();
158 | }
159 | return "";
160 | }
161 |
162 | private static void closeSilent(Closeable closeable) {
163 | try {
164 | closeable.close();
165 | } catch (Exception e) {
166 | // ignore
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/library/src/main/java/com/smartmobilefactory/epubreader/utils/BaseDisposableObserver.kt:
--------------------------------------------------------------------------------
1 | package com.smartmobilefactory.epubreader.utils
2 |
3 | import io.reactivex.SingleObserver
4 | import io.reactivex.disposables.CompositeDisposable
5 | import io.reactivex.observers.DisposableObserver
6 |
7 | internal class BaseDisposableObserver : DisposableObserver(), SingleObserver {
8 | override fun onNext(o: E) {
9 |
10 | }
11 |
12 | override fun onSuccess(e: E) {
13 |
14 | }
15 |
16 | override fun onError(e: Throwable) {
17 | e.printStackTrace()
18 | }
19 |
20 | override fun onComplete() {
21 |
22 | }
23 |
24 | fun addTo(compositeDisposable: CompositeDisposable) {
25 | compositeDisposable.add(this)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/epub_horizontal_vertical_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/epub_vertical_vertical_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/item_epub_vertical_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
25 |
26 |
27 |
28 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/item_vertical_vertical_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/scripts/dumpapp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import os
5 | import io
6 |
7 | from stetho_open import *
8 |
9 | def main():
10 | # Manually parse out -p , all other option handling occurs inside
11 | # the hosting process.
12 |
13 | # Connect to the process passed in via -p. If that is not supplied fallback
14 | # the process defined in STETHO_PROCESS. If neither are defined throw.
15 | process = os.environ.get('STETHO_PROCESS')
16 |
17 | args = sys.argv[1:]
18 | if len(args) > 0 and (args[0] == '-p' or args[0] == '--process'):
19 | if len(args) < 2:
20 | sys.exit('Missing ')
21 | else:
22 | process = args[1]
23 | args = args[2:]
24 |
25 | # Connect to ANDROID_SERIAL if supplied, otherwise fallback to any
26 | # transport.
27 | device = os.environ.get('ANDROID_SERIAL')
28 |
29 | try:
30 | sock = stetho_open(device, process)
31 |
32 | # Send dumpapp hello (DUMP + version=1)
33 | sock.send(b'DUMP' + struct.pack('!L', 1))
34 |
35 | enter_frame = b'!' + struct.pack('!L', len(args))
36 | for arg in args:
37 | argAsUTF8 = arg.encode('utf-8')
38 | enter_frame += struct.pack(
39 | '!H' + str(len(argAsUTF8)) + 's',
40 | len(argAsUTF8),
41 | argAsUTF8)
42 | sock.send(enter_frame)
43 |
44 | read_frames(sock)
45 | except HumanReadableError as e:
46 | sys.exit(e)
47 | except BrokenPipeError as e:
48 | sys.exit(0)
49 | except KeyboardInterrupt:
50 | sys.exit(1)
51 |
52 | def read_frames(sock):
53 | while True:
54 | # All frames have a single character code followed by a big-endian int
55 | code = read_input(sock, 1, 'code')
56 | n = struct.unpack('!L', read_input(sock, 4, 'int4'))[0]
57 |
58 | if code == b'1':
59 | if n > 0:
60 | sys.stdout.buffer.write(read_input(sock, n, 'stdout blob'))
61 | sys.stdout.buffer.flush()
62 | elif code == b'2':
63 | if n > 0:
64 | sys.stderr.buffer.write(read_input(sock, n, 'stderr blob'))
65 | sys.stderr.buffer.flush()
66 | elif code == b'_':
67 | if n > 0:
68 | data = sys.stdin.buffer.read(n)
69 | if len(data) == 0:
70 | sock.send(b'-' + struct.pack('!L', -1))
71 | else:
72 | sock.send(b'-' + struct.pack('!L', len(data)) + data)
73 | elif code == b'x':
74 | sys.exit(n)
75 | else:
76 | if raise_on_eof:
77 | raise IOError('Unexpected header: %s' % code)
78 | break
79 |
80 | if __name__ == '__main__':
81 | main()
82 |
--------------------------------------------------------------------------------
/scripts/hprof_dump.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4 | DUMPAPP="$DIR/dumpapp"
5 |
6 | set -e
7 |
8 | # This will generate an hprof on the device, download it locally, convert the
9 | # hprof to the standard format, and store it in the current working directory.
10 | # The resulting file can be explored with a tool such as the standalone Eclipse
11 | # MemoryAnalyzer: https://eclipse.org/mat/
12 |
13 | if [[ -z "$1" ]]; then
14 | OUTFILE="out.hprof"
15 | else
16 | OUTFILE=$1
17 | fi
18 | TEMPFILE="${OUTFILE}-dalvik.tmp"
19 |
20 | echo "Generating hprof on device (this can take a while)..."
21 | $DUMPAPP "$@" hprof - > ${TEMPFILE}
22 |
23 | echo "Converting $TEMPFILE to standard format..."
24 | hprof-conv $TEMPFILE $OUTFILE
25 | rm $TEMPFILE
26 |
27 | echo "Stored ${OUTFILE}"
28 |
--------------------------------------------------------------------------------
/scripts/stetho_open.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | ###############################################################################
3 | ##
4 | ## Simple utility class to create a forwarded socket connection to an
5 | ## application's stetho domain socket.
6 | ##
7 | ## Usage:
8 | ##
9 | ## sock = stetho_open(
10 | ## device='',
11 | ## process='com.facebook.stetho.sample')
12 | ## doHttp(sock)
13 | ##
14 | ###############################################################################
15 |
16 | import socket
17 | import struct
18 | import re
19 |
20 | def stetho_open(device=None, process=None):
21 | adb = _connect_to_device(device)
22 |
23 | socket_name = None
24 | if process is None:
25 | socket_name = _find_only_stetho_socket(device)
26 | else:
27 | socket_name = _format_process_as_stetho_socket(process)
28 |
29 | try:
30 | adb.select_service('localabstract:%s' % (socket_name))
31 | except SelectServiceError as e:
32 | raise HumanReadableError(
33 | 'Failure to target process %s: %s (is it running?)' % (
34 | process, e.reason))
35 |
36 | return adb.sock
37 |
38 | def read_input(sock, n, tag):
39 | data = b'';
40 | while len(data) < n:
41 | incoming_data = sock.recv(n - len(data))
42 | if len(incoming_data) == 0:
43 | break
44 | data += incoming_data
45 | if len(data) != n:
46 | raise IOError('Unexpected end of stream while reading %s.' % tag)
47 | return data
48 |
49 | def _find_only_stetho_socket(device):
50 | adb = _connect_to_device(device)
51 | try:
52 | adb.select_service('shell:cat /proc/net/unix')
53 | last_stetho_socket_name = None
54 | process_names = []
55 | for line in adb.sock.makefile():
56 | row = line.rstrip().split(' ')
57 | if len(row) < 8:
58 | continue
59 | socket_name = row[7]
60 | if not socket_name.startswith('@stetho_'):
61 | continue
62 | # Filter out entries that are not server sockets
63 | if int(row[3], 16) != 0x10000 or int(row[5]) != 1:
64 | continue
65 | last_stetho_socket_name = socket_name[1:]
66 | process_names.append(
67 | _parse_process_from_stetho_socket(socket_name))
68 | if len(process_names) > 1:
69 | raise HumanReadableError(
70 | 'Multiple stetho-enabled processes available:%s\n' % (
71 | '\n\t'.join([''] + list(set(process_names)))) +
72 | 'Use -p or the environment variable STETHO_PROCESS to ' +
73 | 'select one')
74 | elif last_stetho_socket_name == None:
75 | raise HumanReadableError('No stetho-enabled processes running')
76 | else:
77 | return last_stetho_socket_name
78 | finally:
79 | adb.sock.close()
80 |
81 | def _connect_to_device(device=None):
82 | adb = AdbSmartSocketClient()
83 | adb.connect()
84 |
85 | try:
86 | if device is None:
87 | adb.select_service('host:transport-any')
88 | else:
89 | adb.select_service('host:transport:%s' % (device))
90 |
91 | return adb
92 | except SelectServiceError as e:
93 | raise HumanReadableError(
94 | 'Failure to target device %s: %s' % (device, e.reason))
95 |
96 | def _parse_process_from_stetho_socket(socket_name):
97 | m = re.match("^\@stetho_(.+)_devtools_remote$", socket_name)
98 | if m is None:
99 | raise Exception('Unexpected Stetho socket formatting: %s' % (socket_name))
100 | return m.group(1)
101 |
102 | def _format_process_as_stetho_socket(process):
103 | return 'stetho_%s_devtools_remote' % (process)
104 |
105 | class AdbSmartSocketClient(object):
106 | """Implements the smartsockets system defined by:
107 | https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
108 | """
109 |
110 | def __init__(self):
111 | pass
112 |
113 | def connect(self, port=5037):
114 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
115 | sock.connect(('127.0.0.1', port))
116 | self.sock = sock
117 |
118 | def select_service(self, service):
119 | message = '%04x%s' % (len(service), service)
120 | self.sock.send(message.encode('ascii'))
121 | status = read_input(self.sock, 4, "status")
122 | if status == b'OKAY':
123 | # All good...
124 | pass
125 | elif status == b'FAIL':
126 | reason_len = int(read_input(self.sock, 4, "fail reason"), 16)
127 | reason = read_input(self.sock, reason_len, "fail reason lean").decode('ascii')
128 | raise SelectServiceError(reason)
129 | else:
130 | raise Exception('Unrecognized status=%s' % (status))
131 |
132 | class SelectServiceError(Exception):
133 | def __init__(self, reason):
134 | self.reason = reason
135 |
136 | def __str__(self):
137 | return repr(self.reason)
138 |
139 | class HumanReadableError(Exception):
140 | def __init__(self, reason):
141 | self.reason = reason
142 |
143 | def __str__(self):
144 | return self.reason
145 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------