├── .gitignore
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── assets
├── chuck.gif
└── multiwindow.gif
├── build.gradle
├── gradle.properties
├── gradle
├── gradle-mvn-push.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ic_launcher-web.png
├── library-no-op
├── .gitignore
├── build.gradle
├── gradle.properties
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── readystatesoftware
│ └── chuck
│ ├── Chuck.java
│ └── ChuckInterceptor.java
├── library
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── readystatesoftware
│ │ └── chuck
│ │ ├── Chuck.java
│ │ ├── ChuckInterceptor.java
│ │ └── internal
│ │ ├── data
│ │ ├── ChuckContentProvider.java
│ │ ├── ChuckDbOpenHelper.java
│ │ ├── HttpHeader.java
│ │ ├── HttpTransaction.java
│ │ └── LocalCupboard.java
│ │ ├── support
│ │ ├── ClearTransactionsService.java
│ │ ├── FormatUtils.java
│ │ ├── JsonConvertor.java
│ │ ├── NotificationHelper.java
│ │ ├── RetentionManager.java
│ │ ├── SQLiteUtils.java
│ │ └── SimpleOnPageChangedListener.java
│ │ └── ui
│ │ ├── BaseChuckActivity.java
│ │ ├── MainActivity.java
│ │ ├── TransactionActivity.java
│ │ ├── TransactionAdapter.java
│ │ ├── TransactionFragment.java
│ │ ├── TransactionListFragment.java
│ │ ├── TransactionOverviewFragment.java
│ │ └── TransactionPayloadFragment.java
│ └── res
│ ├── drawable
│ ├── chuck_ic_delete_white_24dp.xml
│ ├── chuck_ic_https_grey_24dp.xml
│ ├── chuck_ic_notification_white_24dp.xml
│ ├── chuck_ic_search_white_24dp.xml
│ └── chuck_ic_share_white_24dp.xml
│ ├── layout
│ ├── chuck_activity_main.xml
│ ├── chuck_activity_transaction.xml
│ ├── chuck_fragment_transaction_list.xml
│ ├── chuck_fragment_transaction_overview.xml
│ ├── chuck_fragment_transaction_payload.xml
│ └── chuck_list_item_transaction.xml
│ ├── menu
│ ├── chuck_main.xml
│ └── chuck_transaction.xml
│ ├── values-v21
│ └── styles.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── readystatesoftware
│ │ └── chuck
│ │ └── sample
│ │ ├── MainActivity.java
│ │ └── SampleApiService.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | /.idea
3 | .gradle
4 | /local.properties
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | Version 1.1.0 *(2017-08-06)*
5 | ----------------------------
6 |
7 | * Fix: Supports apps targeting Android O (API 26).
8 |
9 | Version 1.0.4 *(2017-02-22)*
10 | ----------------------------
11 |
12 | * New: Displays uncompressed gzip encoded request/response bodies when used as a network interceptor.
13 |
14 | Version 1.0.3 *(2017-02-14)*
15 | ----------------------------
16 |
17 | * New: Adds a maximum content length threshold, beyond which bodies are truncated.
18 | * New: Adds a data retention length property and cleanup task.
19 | * New: Adds a clear action to the notification.
20 | * Fix: Mitigates against CursorWindow blowout when transactions are large.
21 |
22 | Version 1.0.2 *(2017-02-10)*
23 | ----------------------------
24 |
25 | * Fix: Added Proguard rule for compat SearchView.
26 | * Fix: Null search query displaying invalid results.
27 |
28 | Version 1.0.1 *(2017-02-09)*
29 | ----------------------------
30 |
31 | * New: Adds a search action which filters on request path or response code.
32 | * New: Adds a transaction count to the notification.
33 | * Fix: Limits the size of the static transaction buffer correctly.
34 |
35 | Version 1.0.0 *(2017-02-07)*
36 | ----------------------------
37 |
38 | Initial release.
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Chuck
2 | =====
3 |
4 | Chuck is a simple in-app HTTP inspector for Android OkHttp clients. Chuck intercepts and persists all HTTP requests and responses inside your application, and provides a UI for inspecting their content.
5 |
6 | 
7 |
8 | Apps using Chuck will display a notification showing a summary of ongoing HTTP activity. Tapping on the notification launches the full Chuck UI. Apps can optionally suppress the notification, and launch the Chuck UI directly from within their own interface. HTTP interactions and their contents can be exported via a share intent.
9 |
10 | The main Chuck activity is launched in its own task, allowing it to be displayed alongside the host app UI using Android 7.x multi-window support.
11 |
12 | 
13 |
14 | Chuck requires Android 4.1+ and OkHttp 3.x.
15 |
16 | **Warning**: The data generated and stored when using this interceptor may contain sensitive information such as Authorization or Cookie headers, and the contents of request and response bodies. It is intended for use during development, and not in release builds or other production deployments.
17 |
18 | Setup
19 | -----
20 |
21 | Add the dependency in your `build.gradle` file. Add it alongside the `no-op` variant to isolate Chuck from release builds as follows:
22 |
23 | ```gradle
24 | dependencies {
25 | debugCompile 'com.readystatesoftware.chuck:library:1.1.0'
26 | releaseCompile 'com.readystatesoftware.chuck:library-no-op:1.1.0'
27 | }
28 | ```
29 |
30 | In your application code, create an instance of `ChuckInterceptor` (you'll need to provide it with a `Context`, because Android) and add it as an interceptor when building your OkHttp client:
31 |
32 | ```java
33 | OkHttpClient client = new OkHttpClient.Builder()
34 | .addInterceptor(new ChuckInterceptor(context))
35 | .build();
36 | ```
37 |
38 | That's it! Chuck will now record all HTTP interactions made by your OkHttp client. You can optionally disable the notification by calling `showNotification(false)` on the interceptor instance, and launch the Chuck UI directly within your app with the intent from `Chuck.getLaunchIntent()`.
39 |
40 | FAQ
41 | ---
42 |
43 | - Why are some of my request headers missing?
44 | - Why are retries and redirects not being captured discretely?
45 | - Why are my encoded request/response bodies not appearing as plain text?
46 |
47 | Please refer to [this section of the OkHttp wiki](https://github.com/square/okhttp/wiki/Interceptors#choosing-between-application-and-network-interceptors). You can choose to use Chuck as either an application or network interceptor, depending on your requirements.
48 |
49 | Acknowledgements
50 | ----------------
51 |
52 | Chuck uses the following open source libraries:
53 |
54 | - [OkHttp](https://github.com/square/okhttp) - Copyright Square, Inc.
55 | - [Gson](https://github.com/google/gson) - Copyright Google Inc.
56 | - [Cupboard](https://bitbucket.org/littlerobots/cupboard) - Copyright Little Robots.
57 |
58 | License
59 | -------
60 |
61 | Copyright (C) 2017 Jeff Gilfelt.
62 |
63 | Licensed under the Apache License, Version 2.0 (the "License");
64 | you may not use this file except in compliance with the License.
65 | You may obtain a copy of the License at
66 |
67 | http://www.apache.org/licenses/LICENSE-2.0
68 |
69 | Unless required by applicable law or agreed to in writing, software
70 | distributed under the License is distributed on an "AS IS" BASIS,
71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72 | See the License for the specific language governing permissions and
73 | limitations under the License.
74 |
--------------------------------------------------------------------------------
/assets/chuck.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/assets/chuck.gif
--------------------------------------------------------------------------------
/assets/multiwindow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/assets/multiwindow.gif
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | version = VERSION_NAME
17 | group = GROUP
18 |
19 | repositories {
20 | jcenter()
21 | maven {
22 | url "https://maven.google.com"
23 | }
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
31 | ext {
32 | minSdkVersion = 16
33 | targetSdkVersion = 26
34 | compileSdkVersion = 26
35 | buildToolsVersion = '26.0.1'
36 |
37 | supportLibVersion = '25.3.1'
38 | okhttp3Version = '3.6.0'
39 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | VERSION_NAME=1.1.0
20 | VERSION_CODE=110
21 | GROUP=com.readystatesoftware.chuck
22 |
23 | POM_DESCRIPTION=Android in-app HTTP inspector
24 | POM_URL=https://github.com/jgilfelt/chuck
25 | POM_SCM_URL=https://github.com/jgilfelt/chuck
26 | POM_SCM_CONNECTION=scm:git@github.com:jgilfelt/chuck.git
27 | POM_SCM_DEV_CONNECTION=scm:git@github.com:jgilfelt/chuck.git
28 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
29 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
30 | POM_LICENCE_DIST=repo
31 | POM_DEVELOPER_ID=jgilfelt
32 | POM_DEVELOPER_NAME=Jeff Gilfelt
--------------------------------------------------------------------------------
/gradle/gradle-mvn-push.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 | apply plugin: 'signing'
3 |
4 | def isReleaseBuild() {
5 | return VERSION_NAME.contains("SNAPSHOT") == false
6 | }
7 |
8 | def getReleaseRepositoryUrl() {
9 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
10 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
11 | }
12 |
13 | def getSnapshotRepositoryUrl() {
14 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
15 | : "https://oss.sonatype.org/content/repositories/snapshots/"
16 | }
17 |
18 | def getRepositoryUsername() {
19 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : ""
20 | }
21 |
22 | def getRepositoryPassword() {
23 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : ""
24 | }
25 |
26 | afterEvaluate { project ->
27 | uploadArchives {
28 | repositories {
29 | mavenDeployer {
30 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
31 |
32 | pom.groupId = GROUP
33 | pom.artifactId = POM_ARTIFACT_ID
34 | pom.version = VERSION_NAME
35 |
36 | repository(url: getReleaseRepositoryUrl()) {
37 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
38 | }
39 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
40 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
41 | }
42 |
43 | pom.project {
44 | name POM_NAME
45 | packaging POM_PACKAGING
46 | description POM_DESCRIPTION
47 | url POM_URL
48 |
49 | scm {
50 | url POM_SCM_URL
51 | connection POM_SCM_CONNECTION
52 | developerConnection POM_SCM_DEV_CONNECTION
53 | }
54 |
55 | licenses {
56 | license {
57 | name POM_LICENCE_NAME
58 | url POM_LICENCE_URL
59 | distribution POM_LICENCE_DIST
60 | }
61 | }
62 |
63 | developers {
64 | developer {
65 | id POM_DEVELOPER_ID
66 | name POM_DEVELOPER_NAME
67 | }
68 | }
69 | }
70 |
71 | }
72 | }
73 | }
74 |
75 | signing {
76 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
77 | sign configurations.archives
78 | }
79 |
80 | task androidJavadocs(type: Javadoc) {
81 | failOnError = false
82 | source = android.sourceSets.main.java.srcDirs
83 | ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
84 | classpath += files(ext.androidJar)
85 | }
86 |
87 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
88 | classifier = 'javadoc'
89 | from androidJavadocs.destinationDir
90 | }
91 |
92 | task androidSourcesJar(type: Jar) {
93 | classifier = 'sources'
94 | from android.sourceSets.main.java.sourceFiles
95 | }
96 |
97 | if (JavaVersion.current().isJava8Compatible()) {
98 | allprojects {
99 | tasks.withType(Javadoc) {
100 | options.addStringOption('Xdoclint:none', '-quiet')
101 | }
102 | }
103 | }
104 |
105 | artifacts {
106 | archives androidSourcesJar
107 | archives androidJavadocsJar
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 15 11:04:59 GMT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/ic_launcher-web.png
--------------------------------------------------------------------------------
/library-no-op/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library-no-op/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | }
10 | }
11 |
12 | dependencies {
13 | compile "com.squareup.okhttp3:okhttp:$okhttp3Version"
14 | }
15 |
16 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
--------------------------------------------------------------------------------
/library-no-op/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=library-no-op
2 | POM_NAME=Chuck no-op
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/library-no-op/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
--------------------------------------------------------------------------------
/library-no-op/src/main/java/com/readystatesoftware/chuck/Chuck.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck;
17 |
18 | import android.content.Context;
19 | import android.content.Intent;
20 |
21 | /**
22 | * No-op implementation.
23 | */
24 | public class Chuck {
25 |
26 | public static Intent getLaunchIntent(Context context) {
27 | return new Intent();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/library-no-op/src/main/java/com/readystatesoftware/chuck/ChuckInterceptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck;
17 |
18 | import android.content.Context;
19 |
20 | import java.io.IOException;
21 |
22 | import okhttp3.Interceptor;
23 | import okhttp3.Request;
24 | import okhttp3.Response;
25 |
26 | /**
27 | * No-op implementation.
28 | */
29 | public final class ChuckInterceptor implements Interceptor {
30 |
31 | public enum Period {
32 | ONE_HOUR,
33 | ONE_DAY,
34 | ONE_WEEK,
35 | FOREVER
36 | }
37 |
38 | public ChuckInterceptor(Context context) {
39 | }
40 |
41 | public ChuckInterceptor showNotification(boolean show) {
42 | return this;
43 | }
44 |
45 | public ChuckInterceptor maxContentLength(long max) {
46 | return this;
47 | }
48 |
49 | public ChuckInterceptor retainDataFor(Period period) {
50 | return this;
51 | }
52 |
53 | @Override
54 | public Response intercept(Chain chain) throws IOException {
55 | Request request = chain.request();
56 | return chain.proceed(request);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | consumerProguardFiles 'proguard-rules.pro'
10 | }
11 | }
12 |
13 | dependencies {
14 | compile 'com.google.code.gson:gson:2.8.0'
15 | compile "com.squareup.okhttp3:okhttp:$okhttp3Version"
16 | compile 'nl.qbusict:cupboard:2.2.0'
17 | compile "com.android.support:design:$supportLibVersion"
18 | }
19 |
20 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
21 |
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=library
2 | POM_NAME=Chuck
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class com.readystatesoftware.chuck.internal.data.HttpTransaction { *; }
2 | -keep class android.support.v7.widget.SearchView { *; }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
24 |
30 |
33 |
36 |
37 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/Chuck.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck;
17 |
18 | import android.content.Context;
19 | import android.content.Intent;
20 |
21 | import com.readystatesoftware.chuck.internal.ui.MainActivity;
22 |
23 | /**
24 | * Chuck utilities.
25 | */
26 | public class Chuck {
27 |
28 | /**
29 | * Get an Intent to launch the Chuck UI directly.
30 | *
31 | * @param context A Context.
32 | * @return An Intent for the main Chuck Activity that can be started with {@link Context#startActivity(Intent)}.
33 | */
34 | public static Intent getLaunchIntent(Context context) {
35 | return new Intent(context, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
36 | }
37 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/ChuckInterceptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Square, Inc, 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck;
17 |
18 | import android.content.ContentValues;
19 | import android.content.Context;
20 | import android.net.Uri;
21 | import android.util.Log;
22 |
23 | import com.readystatesoftware.chuck.internal.data.ChuckContentProvider;
24 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
25 | import com.readystatesoftware.chuck.internal.data.LocalCupboard;
26 | import com.readystatesoftware.chuck.internal.support.NotificationHelper;
27 | import com.readystatesoftware.chuck.internal.support.RetentionManager;
28 |
29 | import java.io.EOFException;
30 | import java.io.IOException;
31 | import java.nio.charset.Charset;
32 | import java.nio.charset.UnsupportedCharsetException;
33 | import java.util.Date;
34 | import java.util.concurrent.TimeUnit;
35 |
36 | import okhttp3.Headers;
37 | import okhttp3.Interceptor;
38 | import okhttp3.MediaType;
39 | import okhttp3.Request;
40 | import okhttp3.RequestBody;
41 | import okhttp3.Response;
42 | import okhttp3.ResponseBody;
43 | import okhttp3.internal.http.HttpHeaders;
44 | import okio.Buffer;
45 | import okio.BufferedSource;
46 | import okio.GzipSource;
47 | import okio.Okio;
48 |
49 | /**
50 | * An OkHttp Interceptor which persists and displays HTTP activity in your application for later inspection.
51 | */
52 | public final class ChuckInterceptor implements Interceptor {
53 |
54 | public enum Period {
55 | /**
56 | * Retain data for the last hour.
57 | */
58 | ONE_HOUR,
59 | /**
60 | * Retain data for the last day.
61 | */
62 | ONE_DAY,
63 | /**
64 | * Retain data for the last week.
65 | */
66 | ONE_WEEK,
67 | /**
68 | * Retain data forever.
69 | */
70 | FOREVER
71 | }
72 |
73 | private static final String LOG_TAG = "ChuckInterceptor";
74 | private static final Period DEFAULT_RETENTION = Period.ONE_WEEK;
75 | private static final Charset UTF8 = Charset.forName("UTF-8");
76 |
77 | private final Context context;
78 | private final NotificationHelper notificationHelper;
79 | private RetentionManager retentionManager;
80 | private boolean showNotification;
81 | private long maxContentLength = 250000L;
82 |
83 | /**
84 | * @param context The current Context.
85 | */
86 | public ChuckInterceptor(Context context) {
87 | this.context = context.getApplicationContext();
88 | notificationHelper = new NotificationHelper(this.context);
89 | showNotification = true;
90 | retentionManager = new RetentionManager(this.context, DEFAULT_RETENTION);
91 | }
92 |
93 | /**
94 | * Control whether a notification is shown while HTTP activity is recorded.
95 | *
96 | * @param show true to show a notification, false to suppress it.
97 | * @return The {@link ChuckInterceptor} instance.
98 | */
99 | public ChuckInterceptor showNotification(boolean show) {
100 | showNotification = show;
101 | return this;
102 | }
103 |
104 | /**
105 | * Set the maximum length for request and response content before it is truncated.
106 | * Warning: setting this value too high may cause unexpected results.
107 | *
108 | * @param max the maximum length (in bytes) for request/response content.
109 | * @return The {@link ChuckInterceptor} instance.
110 | */
111 | public ChuckInterceptor maxContentLength(long max) {
112 | this.maxContentLength = max;
113 | return this;
114 | }
115 |
116 | /**
117 | * Set the retention period for HTTP transaction data captured by this interceptor.
118 | * The default is one week.
119 | *
120 | * @param period the peroid for which to retain HTTP transaction data.
121 | * @return The {@link ChuckInterceptor} instance.
122 | */
123 | public ChuckInterceptor retainDataFor(Period period) {
124 | retentionManager = new RetentionManager(context, period);
125 | return this;
126 | }
127 |
128 | @Override public Response intercept(Chain chain) throws IOException {
129 | Request request = chain.request();
130 |
131 | RequestBody requestBody = request.body();
132 | boolean hasRequestBody = requestBody != null;
133 |
134 | HttpTransaction transaction = new HttpTransaction();
135 | transaction.setRequestDate(new Date());
136 |
137 | transaction.setMethod(request.method());
138 | transaction.setUrl(request.url().toString());
139 |
140 | transaction.setRequestHeaders(request.headers());
141 | if (hasRequestBody) {
142 | if (requestBody.contentType() != null) {
143 | transaction.setRequestContentType(requestBody.contentType().toString());
144 | }
145 | if (requestBody.contentLength() != -1) {
146 | transaction.setRequestContentLength(requestBody.contentLength());
147 | }
148 | }
149 |
150 | transaction.setRequestBodyIsPlainText(!bodyHasUnsupportedEncoding(request.headers()));
151 | if (hasRequestBody && transaction.requestBodyIsPlainText()) {
152 | BufferedSource source = getNativeSource(new Buffer(), bodyGzipped(request.headers()));
153 | Buffer buffer = source.buffer();
154 | requestBody.writeTo(buffer);
155 | Charset charset = UTF8;
156 | MediaType contentType = requestBody.contentType();
157 | if (contentType != null) {
158 | charset = contentType.charset(UTF8);
159 | }
160 | if (isPlaintext(buffer)) {
161 | transaction.setRequestBody(readFromBuffer(buffer, charset));
162 | } else {
163 | transaction.setResponseBodyIsPlainText(false);
164 | }
165 | }
166 |
167 | Uri transactionUri = create(transaction);
168 |
169 | long startNs = System.nanoTime();
170 | Response response;
171 | try {
172 | response = chain.proceed(request);
173 | } catch (Exception e) {
174 | transaction.setError(e.toString());
175 | update(transaction, transactionUri);
176 | throw e;
177 | }
178 | long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
179 |
180 | ResponseBody responseBody = response.body();
181 |
182 | transaction.setRequestHeaders(response.request().headers()); // includes headers added later in the chain
183 | transaction.setResponseDate(new Date());
184 | transaction.setTookMs(tookMs);
185 | transaction.setProtocol(response.protocol().toString());
186 | transaction.setResponseCode(response.code());
187 | transaction.setResponseMessage(response.message());
188 |
189 | transaction.setResponseContentLength(responseBody.contentLength());
190 | if (responseBody.contentType() != null) {
191 | transaction.setResponseContentType(responseBody.contentType().toString());
192 | }
193 | transaction.setResponseHeaders(response.headers());
194 |
195 | transaction.setResponseBodyIsPlainText(!bodyHasUnsupportedEncoding(response.headers()));
196 | if (HttpHeaders.hasBody(response) && transaction.responseBodyIsPlainText()) {
197 | BufferedSource source = getNativeSource(response);
198 | source.request(Long.MAX_VALUE);
199 | Buffer buffer = source.buffer();
200 | Charset charset = UTF8;
201 | MediaType contentType = responseBody.contentType();
202 | if (contentType != null) {
203 | try {
204 | charset = contentType.charset(UTF8);
205 | } catch (UnsupportedCharsetException e) {
206 | update(transaction, transactionUri);
207 | return response;
208 | }
209 | }
210 | if (isPlaintext(buffer)) {
211 | transaction.setResponseBody(readFromBuffer(buffer.clone(), charset));
212 | } else {
213 | transaction.setResponseBodyIsPlainText(false);
214 | }
215 | transaction.setResponseContentLength(buffer.size());
216 | }
217 |
218 | update(transaction, transactionUri);
219 |
220 | return response;
221 | }
222 |
223 | private Uri create(HttpTransaction transaction) {
224 | ContentValues values = LocalCupboard.getInstance().withEntity(HttpTransaction.class).toContentValues(transaction);
225 | Uri uri = context.getContentResolver().insert(ChuckContentProvider.TRANSACTION_URI, values);
226 | transaction.setId(Long.valueOf(uri.getLastPathSegment()));
227 | if (showNotification) {
228 | notificationHelper.show(transaction);
229 | }
230 | retentionManager.doMaintenance();
231 | return uri;
232 | }
233 |
234 | private int update(HttpTransaction transaction, Uri uri) {
235 | ContentValues values = LocalCupboard.getInstance().withEntity(HttpTransaction.class).toContentValues(transaction);
236 | int updated = context.getContentResolver().update(uri, values, null, null);
237 | if (showNotification && updated > 0) {
238 | notificationHelper.show(transaction);
239 | }
240 | return updated;
241 | }
242 |
243 | /**
244 | * Returns true if the body in question probably contains human readable text. Uses a small sample
245 | * of code points to detect unicode control characters commonly used in binary file signatures.
246 | */
247 | private boolean isPlaintext(Buffer buffer) {
248 | try {
249 | Buffer prefix = new Buffer();
250 | long byteCount = buffer.size() < 64 ? buffer.size() : 64;
251 | buffer.copyTo(prefix, 0, byteCount);
252 | for (int i = 0; i < 16; i++) {
253 | if (prefix.exhausted()) {
254 | break;
255 | }
256 | int codePoint = prefix.readUtf8CodePoint();
257 | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
258 | return false;
259 | }
260 | }
261 | return true;
262 | } catch (EOFException e) {
263 | return false; // Truncated UTF-8 sequence.
264 | }
265 | }
266 |
267 | private boolean bodyHasUnsupportedEncoding(Headers headers) {
268 | String contentEncoding = headers.get("Content-Encoding");
269 | return contentEncoding != null &&
270 | !contentEncoding.equalsIgnoreCase("identity") &&
271 | !contentEncoding.equalsIgnoreCase("gzip");
272 | }
273 |
274 | private boolean bodyGzipped(Headers headers) {
275 | String contentEncoding = headers.get("Content-Encoding");
276 | return "gzip".equalsIgnoreCase(contentEncoding);
277 | }
278 |
279 | private String readFromBuffer(Buffer buffer, Charset charset) {
280 | long bufferSize = buffer.size();
281 | long maxBytes = Math.min(bufferSize, maxContentLength);
282 | String body = "";
283 | try {
284 | body = buffer.readString(maxBytes, charset);
285 | } catch (EOFException e) {
286 | body += context.getString(R.string.chuck_body_unexpected_eof);
287 | }
288 | if (bufferSize > maxContentLength) {
289 | body += context.getString(R.string.chuck_body_content_truncated);
290 | }
291 | return body;
292 | }
293 |
294 | private BufferedSource getNativeSource(BufferedSource input, boolean isGzipped) {
295 | if (isGzipped) {
296 | GzipSource source = new GzipSource(input);
297 | return Okio.buffer(source);
298 | } else {
299 | return input;
300 | }
301 | }
302 |
303 | private BufferedSource getNativeSource(Response response) throws IOException {
304 | if (bodyGzipped(response.headers())) {
305 | BufferedSource source = response.peekBody(maxContentLength).source();
306 | if (source.buffer().size() < maxContentLength) {
307 | return getNativeSource(source, true);
308 | } else {
309 | Log.w(LOG_TAG, "gzip encoded response was too long");
310 | }
311 | }
312 | return response.body().source();
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/data/ChuckContentProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.data;
17 |
18 | import android.content.ContentProvider;
19 | import android.content.ContentUris;
20 | import android.content.ContentValues;
21 | import android.content.Context;
22 | import android.content.UriMatcher;
23 | import android.content.pm.ProviderInfo;
24 | import android.database.Cursor;
25 | import android.database.sqlite.SQLiteDatabase;
26 | import android.net.Uri;
27 | import android.support.annotation.NonNull;
28 | import android.support.annotation.Nullable;
29 |
30 | public class ChuckContentProvider extends ContentProvider {
31 |
32 | public static Uri TRANSACTION_URI;
33 |
34 | private static final int TRANSACTION = 0;
35 | private static final int TRANSACTIONS = 1;
36 | private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
37 |
38 | private ChuckDbOpenHelper databaseHelper;
39 |
40 | @Override
41 | public void attachInfo(Context context, ProviderInfo info) {
42 | super.attachInfo(context, info);
43 | TRANSACTION_URI = Uri.parse("content://" + info.authority + "/transaction");
44 | matcher.addURI(info.authority, "transaction/#", TRANSACTION);
45 | matcher.addURI(info.authority, "transaction", TRANSACTIONS);
46 | }
47 |
48 | @Override
49 | public boolean onCreate() {
50 | databaseHelper = new ChuckDbOpenHelper(getContext());
51 | return true;
52 | }
53 |
54 | @Override
55 | @Nullable
56 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
57 | @Nullable String selection, @Nullable String[] selectionArgs,
58 | @Nullable String sortOrder) {
59 | SQLiteDatabase db = databaseHelper.getWritableDatabase();
60 | Cursor cursor = null;
61 | switch (matcher.match(uri)) {
62 | case TRANSACTIONS:
63 | cursor = LocalCupboard.getInstance().withDatabase(db).query(HttpTransaction.class).
64 | withProjection(projection).
65 | withSelection(selection, selectionArgs).
66 | orderBy(sortOrder).
67 | getCursor();
68 | break;
69 | case TRANSACTION:
70 | cursor = LocalCupboard.getInstance().withDatabase(db).query(HttpTransaction.class).
71 | byId(ContentUris.parseId(uri)).
72 | getCursor();
73 | break;
74 | }
75 | if (cursor != null) {
76 | cursor.setNotificationUri(getContext().getContentResolver(), uri);
77 | }
78 | return cursor;
79 | }
80 |
81 | @Override
82 | @Nullable
83 | public String getType(@NonNull Uri uri) {
84 | return null;
85 | }
86 |
87 | @Override
88 | @Nullable
89 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
90 | SQLiteDatabase db = databaseHelper.getWritableDatabase();
91 | switch (matcher.match(uri)) {
92 | case TRANSACTIONS:
93 | long id = db.insert(LocalCupboard.getInstance().getTable(HttpTransaction.class), null, contentValues);
94 | if (id > 0) {
95 | getContext().getContentResolver().notifyChange(uri, null);
96 | return ContentUris.withAppendedId(TRANSACTION_URI, id);
97 | }
98 | }
99 | return null;
100 | }
101 |
102 | @Override
103 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
104 | SQLiteDatabase db = databaseHelper.getWritableDatabase();
105 | int result = 0;
106 | switch (matcher.match(uri)) {
107 | case TRANSACTIONS:
108 | result = db.delete(LocalCupboard.getInstance().getTable(HttpTransaction.class), selection, selectionArgs);
109 | break;
110 | case TRANSACTION:
111 | result = db.delete(LocalCupboard.getInstance().getTable(HttpTransaction.class),
112 | "_id = ?", new String[]{ uri.getPathSegments().get(1) });
113 | break;
114 | }
115 | if (result > 0) {
116 | getContext().getContentResolver().notifyChange(uri, null);
117 | }
118 | return result;
119 | }
120 |
121 | @Override
122 | public int update(@NonNull Uri uri, @Nullable ContentValues contentValues,
123 | @Nullable String selection, @Nullable String[] selectionArgs) {
124 | SQLiteDatabase db = databaseHelper.getWritableDatabase();
125 | int result = 0;
126 | switch (matcher.match(uri)) {
127 | case TRANSACTIONS:
128 | result = db.update(LocalCupboard.getInstance().getTable(HttpTransaction.class), contentValues, selection, selectionArgs);
129 | break;
130 | case TRANSACTION:
131 | result = db.update(LocalCupboard.getInstance().getTable(HttpTransaction.class), contentValues,
132 | "_id = ?", new String[]{ uri.getPathSegments().get(1) });
133 | break;
134 | }
135 | if (result > 0) {
136 | getContext().getContentResolver().notifyChange(uri, null);
137 | }
138 | return result;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/data/ChuckDbOpenHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.data;
17 |
18 | import android.content.Context;
19 | import android.database.sqlite.SQLiteDatabase;
20 | import android.database.sqlite.SQLiteOpenHelper;
21 |
22 | class ChuckDbOpenHelper extends SQLiteOpenHelper {
23 |
24 | private static final String DATABASE_NAME = "chuck.db";
25 | private static final int VERSION = 3;
26 |
27 | ChuckDbOpenHelper(Context context) {
28 | super(context, DATABASE_NAME, null, VERSION);
29 | }
30 |
31 | @Override
32 | public void onCreate(SQLiteDatabase db) {
33 | LocalCupboard.getAnnotatedInstance().withDatabase(db).createTables();
34 | }
35 |
36 | @Override
37 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
38 | LocalCupboard.getAnnotatedInstance().withDatabase(db).upgradeTables();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/data/HttpHeader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.data;
17 |
18 | public class HttpHeader {
19 |
20 | private final String name;
21 | private final String value;
22 |
23 | HttpHeader(String name, String value) {
24 | this.name = name;
25 | this.value = value;
26 | }
27 |
28 | public String getName() {
29 | return name;
30 | }
31 |
32 | public String getValue() {
33 | return value;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/data/HttpTransaction.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.data;
17 |
18 | import android.net.Uri;
19 |
20 | import com.google.gson.reflect.TypeToken;
21 | import com.readystatesoftware.chuck.internal.support.FormatUtils;
22 | import com.readystatesoftware.chuck.internal.support.JsonConvertor;
23 |
24 | import java.text.SimpleDateFormat;
25 | import java.util.ArrayList;
26 | import java.util.Date;
27 | import java.util.List;
28 | import java.util.Locale;
29 |
30 | import nl.qbusict.cupboard.annotation.Index;
31 | import okhttp3.Headers;
32 |
33 | public class HttpTransaction {
34 |
35 | public enum Status {
36 | Requested,
37 | Complete,
38 | Failed
39 | }
40 |
41 | public static final String[] PARTIAL_PROJECTION = new String[] {
42 | "_id",
43 | "requestDate",
44 | "tookMs",
45 | "method",
46 | "host",
47 | "path",
48 | "scheme",
49 | "requestContentLength",
50 | "responseCode",
51 | "error",
52 | "responseContentLength"
53 | };
54 |
55 | private static final SimpleDateFormat TIME_ONLY_FMT = new SimpleDateFormat("HH:mm:ss", Locale.US);
56 |
57 | private Long _id;
58 | @Index private Date requestDate;
59 | private Date responseDate;
60 | private Long tookMs;
61 |
62 | private String protocol;
63 | private String method;
64 | private String url;
65 | private String host;
66 | private String path;
67 | private String scheme;
68 |
69 | private Long requestContentLength;
70 | private String requestContentType;
71 | private String requestHeaders;
72 | private String requestBody;
73 | private boolean requestBodyIsPlainText = true;
74 |
75 | private Integer responseCode;
76 | private String responseMessage;
77 | private String error;
78 |
79 | private Long responseContentLength;
80 | private String responseContentType;
81 | private String responseHeaders;
82 | private String responseBody;
83 | private boolean responseBodyIsPlainText = true;
84 |
85 | public Long getId() {
86 | return _id;
87 | }
88 |
89 | public void setId(long id) {
90 | _id = id;
91 | }
92 |
93 | public Date getRequestDate() {
94 | return requestDate;
95 | }
96 |
97 | public void setRequestDate(Date requestDate) {
98 | this.requestDate = requestDate;
99 | }
100 |
101 | public Date getResponseDate() {
102 | return responseDate;
103 | }
104 |
105 | public void setResponseDate(Date responseDate) {
106 | this.responseDate = responseDate;
107 | }
108 |
109 | public String getError() {
110 | return error;
111 | }
112 |
113 | public void setError(String error) {
114 | this.error = error;
115 | }
116 |
117 | public String getMethod() {
118 | return method;
119 | }
120 |
121 | public void setMethod(String method) {
122 | this.method = method;
123 | }
124 |
125 | public String getProtocol() {
126 | return protocol;
127 | }
128 |
129 | public void setProtocol(String protocol) {
130 | this.protocol = protocol;
131 | }
132 |
133 | public String getRequestBody() {
134 | return requestBody;
135 | }
136 |
137 | public String getFormattedRequestBody() {
138 | return formatBody(requestBody, requestContentType);
139 | }
140 |
141 | public void setRequestBody(String requestBody) {
142 | this.requestBody = requestBody;
143 | }
144 |
145 | public boolean requestBodyIsPlainText() {
146 | return requestBodyIsPlainText;
147 | }
148 |
149 | public void setRequestBodyIsPlainText(boolean requestBodyIsPlainText) {
150 | this.requestBodyIsPlainText = requestBodyIsPlainText;
151 | }
152 |
153 | public Long getRequestContentLength() {
154 | return requestContentLength;
155 | }
156 |
157 | public void setRequestContentLength(Long requestContentLength) {
158 | this.requestContentLength = requestContentLength;
159 | }
160 |
161 | public String getRequestContentType() {
162 | return requestContentType;
163 | }
164 |
165 | public void setRequestContentType(String requestContentType) {
166 | this.requestContentType = requestContentType;
167 | }
168 |
169 | public String getResponseBody() {
170 | return responseBody;
171 | }
172 |
173 | public String getFormattedResponseBody() {
174 | return formatBody(responseBody, responseContentType);
175 | }
176 |
177 | public void setResponseBody(String responseBody) {
178 | this.responseBody = responseBody;
179 | }
180 |
181 | public boolean responseBodyIsPlainText() {
182 | return responseBodyIsPlainText;
183 | }
184 |
185 | public void setResponseBodyIsPlainText(boolean responseBodyIsPlainText) {
186 | this.responseBodyIsPlainText = responseBodyIsPlainText;
187 | }
188 |
189 | public Integer getResponseCode() {
190 | return responseCode;
191 | }
192 |
193 | public void setResponseCode(Integer responseCode) {
194 | this.responseCode = responseCode;
195 | }
196 |
197 | public Long getResponseContentLength() {
198 | return responseContentLength;
199 | }
200 |
201 | public void setResponseContentLength(Long responseContentLength) {
202 | this.responseContentLength = responseContentLength;
203 | }
204 |
205 | public String getResponseContentType() {
206 | return responseContentType;
207 | }
208 |
209 | public void setResponseContentType(String responseContentType) {
210 | this.responseContentType = responseContentType;
211 | }
212 |
213 | public String getResponseMessage() {
214 | return responseMessage;
215 | }
216 |
217 | public void setResponseMessage(String responseMessage) {
218 | this.responseMessage = responseMessage;
219 | }
220 |
221 | public Long getTookMs() {
222 | return tookMs;
223 | }
224 |
225 | public void setTookMs(Long tookMs) {
226 | this.tookMs = tookMs;
227 | }
228 |
229 | public String getUrl() {
230 | return url;
231 | }
232 |
233 | public void setUrl(String url) {
234 | this.url = url;
235 | Uri uri = Uri.parse(url);
236 | host = uri.getHost();
237 | path = uri.getPath() + ((uri.getQuery() != null) ? "?" + uri.getQuery() : "");
238 | scheme = uri.getScheme();
239 | }
240 |
241 | public String getHost() {
242 | return host;
243 | }
244 |
245 | public String getPath() {
246 | return path;
247 | }
248 |
249 | public String getScheme() {
250 | return scheme;
251 | }
252 |
253 | public void setRequestHeaders(Headers headers) {
254 | setRequestHeaders(toHttpHeaderList(headers));
255 | }
256 |
257 | public void setRequestHeaders(List headers) {
258 | requestHeaders = JsonConvertor.getInstance().toJson(headers);
259 | }
260 |
261 | public List getRequestHeaders() {
262 | return JsonConvertor.getInstance().fromJson(requestHeaders,
263 | new TypeToken>(){}.getType());
264 | }
265 |
266 | public String getRequestHeadersString(boolean withMarkup) {
267 | return FormatUtils.formatHeaders(getRequestHeaders(), withMarkup);
268 | }
269 |
270 | public void setResponseHeaders(Headers headers) {
271 | setResponseHeaders(toHttpHeaderList(headers));
272 | }
273 |
274 | public void setResponseHeaders(List headers) {
275 | responseHeaders = JsonConvertor.getInstance().toJson(headers);
276 | }
277 |
278 | public List getResponseHeaders() {
279 | return JsonConvertor.getInstance().fromJson(responseHeaders,
280 | new TypeToken>(){}.getType());
281 | }
282 |
283 | public String getResponseHeadersString(boolean withMarkup) {
284 | return FormatUtils.formatHeaders(getResponseHeaders(), withMarkup);
285 | }
286 |
287 | public Status getStatus() {
288 | if (error != null) {
289 | return Status.Failed;
290 | } else if (responseCode == null) {
291 | return Status.Requested;
292 | } else {
293 | return Status.Complete;
294 | }
295 | }
296 |
297 | public String getRequestStartTimeString() {
298 | return (requestDate != null) ? TIME_ONLY_FMT.format(requestDate) : null;
299 | }
300 |
301 | public String getRequestDateString() {
302 | return (requestDate != null) ? requestDate.toString() : null;
303 | }
304 |
305 | public String getResponseDateString() {
306 | return (responseDate != null) ? responseDate.toString() : null;
307 | }
308 |
309 | public String getDurationString() {
310 | return (tookMs != null) ? + tookMs + " ms" : null;
311 | }
312 |
313 | public String getRequestSizeString() {
314 | return formatBytes((requestContentLength != null) ? requestContentLength : 0);
315 | }
316 | public String getResponseSizeString() {
317 | return (responseContentLength != null) ? formatBytes(responseContentLength) : null;
318 | }
319 |
320 | public String getTotalSizeString() {
321 | long reqBytes = (requestContentLength != null) ? requestContentLength : 0;
322 | long resBytes = (responseContentLength != null) ? responseContentLength : 0;
323 | return formatBytes(reqBytes + resBytes);
324 | }
325 |
326 | public String getResponseSummaryText() {
327 | switch (getStatus()) {
328 | case Failed:
329 | return error;
330 | case Requested:
331 | return null;
332 | default:
333 | return String.valueOf(responseCode) + " " + responseMessage;
334 | }
335 | }
336 |
337 | public String getNotificationText() {
338 | switch (getStatus()) {
339 | case Failed:
340 | return " ! ! ! " + path;
341 | case Requested:
342 | return " . . . " + path;
343 | default:
344 | return String.valueOf(responseCode) + " " + path;
345 | }
346 | }
347 |
348 | public boolean isSsl() {
349 | return scheme.toLowerCase().equals("https");
350 | }
351 |
352 | private List toHttpHeaderList(Headers headers) {
353 | List httpHeaders = new ArrayList<>();
354 | for (int i = 0, count = headers.size(); i < count; i++) {
355 | httpHeaders.add(new HttpHeader(headers.name(i), headers.value(i)));
356 | }
357 | return httpHeaders;
358 | }
359 |
360 | private String formatBody(String body, String contentType) {
361 | if (contentType != null && contentType.toLowerCase().contains("json")) {
362 | return FormatUtils.formatJson(body);
363 | } else if (contentType != null && contentType.toLowerCase().contains("xml")) {
364 | return FormatUtils.formatXml(body);
365 | } else {
366 | return body;
367 | }
368 | }
369 |
370 | private String formatBytes(long bytes) {
371 | return FormatUtils.formatByteCount(bytes, true);
372 | }
373 | }
374 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/data/LocalCupboard.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.data;
17 |
18 | import nl.qbusict.cupboard.Cupboard;
19 | import nl.qbusict.cupboard.CupboardBuilder;
20 |
21 | public class LocalCupboard {
22 |
23 | private static Cupboard cupboard;
24 |
25 | static {
26 | getInstance().register(HttpTransaction.class);
27 | }
28 |
29 | public static Cupboard getInstance() {
30 | if (cupboard == null) {
31 | cupboard = new CupboardBuilder().build();
32 | }
33 | return cupboard;
34 | }
35 |
36 | public static Cupboard getAnnotatedInstance() {
37 | return new CupboardBuilder(getInstance())
38 | .useAnnotations()
39 | .build();
40 | }
41 |
42 | private LocalCupboard() {
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/ClearTransactionsService.java:
--------------------------------------------------------------------------------
1 | package com.readystatesoftware.chuck.internal.support;
2 |
3 | import android.app.IntentService;
4 | import android.content.Intent;
5 | import android.support.annotation.Nullable;
6 |
7 | import com.readystatesoftware.chuck.internal.data.ChuckContentProvider;
8 |
9 | public class ClearTransactionsService extends IntentService {
10 |
11 | public ClearTransactionsService() {
12 | super("Chuck-ClearTransactionsService");
13 | }
14 |
15 | @Override
16 | protected void onHandleIntent(@Nullable Intent intent) {
17 | getContentResolver().delete(ChuckContentProvider.TRANSACTION_URI, null, null);
18 | NotificationHelper.clearBuffer();
19 | NotificationHelper notificationHelper = new NotificationHelper(this);
20 | notificationHelper.dismiss();
21 | }
22 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/FormatUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.support;
17 |
18 | import android.content.Context;
19 | import android.text.TextUtils;
20 |
21 | import com.readystatesoftware.chuck.R;
22 | import com.readystatesoftware.chuck.internal.data.HttpHeader;
23 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
24 | import com.google.gson.JsonElement;
25 | import com.google.gson.JsonParser;
26 |
27 | import org.xml.sax.InputSource;
28 |
29 | import java.io.ByteArrayInputStream;
30 | import java.io.ByteArrayOutputStream;
31 | import java.util.List;
32 | import java.util.Locale;
33 |
34 | import javax.xml.transform.OutputKeys;
35 | import javax.xml.transform.Source;
36 | import javax.xml.transform.Transformer;
37 | import javax.xml.transform.sax.SAXSource;
38 | import javax.xml.transform.sax.SAXTransformerFactory;
39 | import javax.xml.transform.stream.StreamResult;
40 |
41 | public class FormatUtils {
42 |
43 | public static String formatHeaders(List httpHeaders, boolean withMarkup) {
44 | String out = "";
45 | if (httpHeaders != null) {
46 | for (HttpHeader header : httpHeaders) {
47 | out += ((withMarkup) ? "" : "") + header.getName() + ": " + ((withMarkup) ? "" : "") +
48 | header.getValue() + ((withMarkup) ? "
" : "\n");
49 | }
50 | }
51 | return out;
52 | }
53 |
54 | public static String formatByteCount(long bytes, boolean si) {
55 | int unit = si ? 1000 : 1024;
56 | if (bytes < unit) return bytes + " B";
57 | int exp = (int) (Math.log(bytes) / Math.log(unit));
58 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
59 | return String.format(Locale.US, "%.1f %sB", bytes / Math.pow(unit, exp), pre);
60 | }
61 |
62 | public static String formatJson(String json) {
63 | try {
64 | JsonParser jp = new JsonParser();
65 | JsonElement je = jp.parse(json);
66 | return JsonConvertor.getInstance().toJson(je);
67 | } catch (Exception e) {
68 | return json;
69 | }
70 | }
71 |
72 | public static String formatXml(String xml) {
73 | try {
74 | Transformer serializer = SAXTransformerFactory.newInstance().newTransformer();
75 | serializer.setOutputProperty(OutputKeys.INDENT, "yes");
76 | serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
77 | Source xmlSource = new SAXSource(new InputSource(new ByteArrayInputStream(xml.getBytes())));
78 | StreamResult res = new StreamResult(new ByteArrayOutputStream());
79 | serializer.transform(xmlSource, res);
80 | return new String(((ByteArrayOutputStream)res.getOutputStream()).toByteArray());
81 | } catch (Exception e) {
82 | return xml;
83 | }
84 | }
85 |
86 | public static String getShareText(Context context, HttpTransaction transaction) {
87 | String text = "";
88 | text += context.getString(R.string.chuck_url) + ": " + v(transaction.getUrl()) + "\n";
89 | text += context.getString(R.string.chuck_method) + ": " + v(transaction.getMethod()) + "\n";
90 | text += context.getString(R.string.chuck_protocol) + ": " + v(transaction.getProtocol()) + "\n";
91 | text += context.getString(R.string.chuck_status) + ": " + v(transaction.getStatus().toString()) + "\n";
92 | text += context.getString(R.string.chuck_response) + ": " + v(transaction.getResponseSummaryText()) + "\n";
93 | text += context.getString(R.string.chuck_ssl) + ": " + v(context.getString(transaction.isSsl() ? R.string.chuck_yes : R.string.chuck_no)) + "\n";
94 | text += "\n";
95 | text += context.getString(R.string.chuck_request_time) + ": " + v(transaction.getRequestDateString()) + "\n";
96 | text += context.getString(R.string.chuck_response_time) + ": " + v(transaction.getResponseDateString()) + "\n";
97 | text += context.getString(R.string.chuck_duration) + ": " + v(transaction.getDurationString()) + "\n";
98 | text += "\n";
99 | text += context.getString(R.string.chuck_request_size) + ": " + v(transaction.getRequestSizeString()) + "\n";
100 | text += context.getString(R.string.chuck_response_size) + ": " + v(transaction.getResponseSizeString()) + "\n";
101 | text += context.getString(R.string.chuck_total_size) + ": " + v(transaction.getTotalSizeString()) + "\n";
102 | text += "\n";
103 | text += "---------- " + context.getString(R.string.chuck_request) + " ----------\n\n";
104 | String headers = formatHeaders(transaction.getRequestHeaders(), false);
105 | if (!TextUtils.isEmpty(headers)) {
106 | text += headers + "\n";
107 | }
108 | text += (transaction.requestBodyIsPlainText()) ? v(transaction.getFormattedRequestBody()) :
109 | context.getString(R.string.chuck_body_omitted);
110 | text += "\n\n";
111 | text += "---------- " + context.getString(R.string.chuck_response) + " ----------\n\n";
112 | headers = formatHeaders(transaction.getResponseHeaders(), false);
113 | if (!TextUtils.isEmpty(headers)) {
114 | text += headers + "\n";
115 | }
116 | text += (transaction.responseBodyIsPlainText()) ? v(transaction.getFormattedResponseBody()) :
117 | context.getString(R.string.chuck_body_omitted);
118 | return text;
119 | }
120 |
121 | public static String getShareCurlCommand(HttpTransaction transaction) {
122 | boolean compressed = false;
123 | String curlCmd = "curl";
124 | curlCmd += " -X " + transaction.getMethod();
125 | List headers = transaction.getRequestHeaders();
126 | for (int i = 0, count = headers.size(); i < count; i++) {
127 | String name = headers.get(i).getName();
128 | String value = headers.get(i).getValue();
129 | if ("Accept-Encoding".equalsIgnoreCase(name) && "gzip".equalsIgnoreCase(value)) {
130 | compressed = true;
131 | }
132 | curlCmd += " -H " + "\"" + name + ": " + value + "\"";
133 | }
134 | String requestBody = transaction.getRequestBody();
135 | if (requestBody != null && requestBody.length() > 0) {
136 | // try to keep to a single line and use a subshell to preserve any line breaks
137 | curlCmd += " --data $'" + requestBody.replace("\n", "\\n") + "'";
138 | }
139 | curlCmd += ((compressed) ? " --compressed " : " ") + transaction.getUrl();
140 | return curlCmd;
141 | }
142 |
143 | private static String v(String string) {
144 | return (string != null) ? string : "";
145 | }
146 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/JsonConvertor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.support;
17 |
18 | import com.google.gson.FieldNamingPolicy;
19 | import com.google.gson.Gson;
20 | import com.google.gson.GsonBuilder;
21 | import com.google.gson.internal.bind.DateTypeAdapter;
22 |
23 | import java.util.Date;
24 |
25 | public class JsonConvertor {
26 |
27 | private static Gson gson = null;
28 |
29 | private JsonConvertor() {
30 | }
31 |
32 | public static Gson getInstance() {
33 | if (gson == null) {
34 | gson = new GsonBuilder()
35 | .setPrettyPrinting()
36 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
37 | .registerTypeAdapter(Date.class, new DateTypeAdapter())
38 | .create();
39 | }
40 | return gson;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/NotificationHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.support;
17 |
18 | import android.app.NotificationChannel;
19 | import android.app.NotificationManager;
20 | import android.app.PendingIntent;
21 | import android.content.Context;
22 | import android.content.Intent;
23 | import android.os.Build;
24 | import android.support.annotation.NonNull;
25 | import android.support.v4.app.NotificationCompat;
26 | import android.support.v4.content.ContextCompat;
27 | import android.util.LongSparseArray;
28 |
29 | import com.readystatesoftware.chuck.Chuck;
30 | import com.readystatesoftware.chuck.R;
31 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
32 | import com.readystatesoftware.chuck.internal.ui.BaseChuckActivity;
33 |
34 | import java.lang.reflect.Method;
35 |
36 | public class NotificationHelper {
37 |
38 | private static final String CHANNEL_ID = "chuck";
39 | private static final int NOTIFICATION_ID = 1138;
40 | private static final int BUFFER_SIZE = 10;
41 |
42 | private static final LongSparseArray transactionBuffer = new LongSparseArray<>();
43 | private static int transactionCount;
44 |
45 | private final Context context;
46 | private final NotificationManager notificationManager;
47 | private Method setChannelId;
48 |
49 | public static synchronized void clearBuffer() {
50 | transactionBuffer.clear();
51 | transactionCount = 0;
52 | }
53 |
54 | private static synchronized void addToBuffer(HttpTransaction transaction) {
55 | if (transaction.getStatus() == HttpTransaction.Status.Requested) {
56 | transactionCount++;
57 | }
58 | transactionBuffer.put(transaction.getId(), transaction);
59 | if (transactionBuffer.size() > BUFFER_SIZE) {
60 | transactionBuffer.removeAt(0);
61 | }
62 | }
63 |
64 | public NotificationHelper(Context context) {
65 | this.context = context;
66 | notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
67 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
68 | notificationManager.createNotificationChannel(
69 | new NotificationChannel(CHANNEL_ID,
70 | context.getString(R.string.notification_category), NotificationManager.IMPORTANCE_LOW));
71 | try {
72 | setChannelId = NotificationCompat.Builder.class.getMethod("setChannelId", String.class);
73 | } catch (Exception ignored) {}
74 | }
75 | }
76 |
77 | public synchronized void show(HttpTransaction transaction) {
78 | addToBuffer(transaction);
79 | if (!BaseChuckActivity.isInForeground()) {
80 | NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
81 | .setContentIntent(PendingIntent.getActivity(context, 0, Chuck.getLaunchIntent(context), 0))
82 | .setLocalOnly(true)
83 | .setSmallIcon(R.drawable.chuck_ic_notification_white_24dp)
84 | .setColor(ContextCompat.getColor(context, R.color.chuck_colorPrimary))
85 | .setContentTitle(context.getString(R.string.chuck_notification_title));
86 | NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
87 | if (setChannelId != null) {
88 | try { setChannelId.invoke(builder, CHANNEL_ID); } catch (Exception ignored) {}
89 | }
90 | int count = 0;
91 | for (int i = transactionBuffer.size() - 1; i >= 0; i--) {
92 | if (count < BUFFER_SIZE) {
93 | if (count == 0) {
94 | builder.setContentText(transactionBuffer.valueAt(i).getNotificationText());
95 | }
96 | inboxStyle.addLine(transactionBuffer.valueAt(i).getNotificationText());
97 | }
98 | count++;
99 | }
100 | builder.setAutoCancel(true);
101 | builder.setStyle(inboxStyle);
102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
103 | builder.setSubText(String.valueOf(transactionCount));
104 | } else {
105 | builder.setNumber(transactionCount);
106 | }
107 | builder.addAction(getClearAction());
108 | notificationManager.notify(NOTIFICATION_ID, builder.build());
109 | }
110 | }
111 |
112 | @NonNull
113 | private NotificationCompat.Action getClearAction() {
114 | CharSequence clearTitle = context.getString(R.string.chuck_clear);
115 | Intent deleteIntent = new Intent(context, ClearTransactionsService.class);
116 | PendingIntent intent = PendingIntent.getService(context, 11, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
117 | return new NotificationCompat.Action(R.drawable.chuck_ic_delete_white_24dp,
118 | clearTitle, intent);
119 | }
120 |
121 | public void dismiss() {
122 | notificationManager.cancel(NOTIFICATION_ID);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/RetentionManager.java:
--------------------------------------------------------------------------------
1 | package com.readystatesoftware.chuck.internal.support;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.util.Log;
6 |
7 | import com.readystatesoftware.chuck.ChuckInterceptor;
8 | import com.readystatesoftware.chuck.internal.data.ChuckContentProvider;
9 |
10 | import java.util.Date;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | public class RetentionManager {
14 |
15 | private static final String LOG_TAG = "Chuck";
16 | private static final String PREFS_NAME = "chuck_preferences";
17 | private static final String KEY_LAST_CLEANUP = "last_cleanup";
18 |
19 | private static long lastCleanup;
20 |
21 | private final Context context;
22 | private final long period;
23 | private final long cleanupFrequency;
24 | private final SharedPreferences prefs;
25 |
26 | public RetentionManager(Context context, ChuckInterceptor.Period retentionPeriod) {
27 | this.context = context;
28 | period = toMillis(retentionPeriod);
29 | prefs = context.getSharedPreferences(PREFS_NAME, 0);
30 | cleanupFrequency = (retentionPeriod == ChuckInterceptor.Period.ONE_HOUR) ?
31 | TimeUnit.MINUTES.toMillis(30) : TimeUnit.HOURS.toMillis(2);
32 | }
33 |
34 | public synchronized void doMaintenance() {
35 | if (period > 0) {
36 | long now = new Date().getTime();
37 | if (isCleanupDue(now)) {
38 | Log.i(LOG_TAG, "Performing data retention maintenance...");
39 | deleteSince(getThreshold(now));
40 | updateLastCleanup(now);
41 | }
42 | }
43 | }
44 |
45 | private long getLastCleanup(long fallback) {
46 | if (lastCleanup == 0) {
47 | lastCleanup = prefs.getLong(KEY_LAST_CLEANUP, fallback);
48 | }
49 | return lastCleanup;
50 | }
51 |
52 | private void updateLastCleanup(long time) {
53 | lastCleanup = time;
54 | prefs.edit().putLong(KEY_LAST_CLEANUP, time).apply();
55 | }
56 |
57 | private void deleteSince(long threshold) {
58 | int rows = context.getContentResolver().delete(ChuckContentProvider.TRANSACTION_URI,
59 | "requestDate <= ?", new String[] { String.valueOf(threshold) });
60 | Log.i(LOG_TAG, rows + " transactions deleted");
61 | }
62 |
63 | private boolean isCleanupDue(long now) {
64 | return (now - getLastCleanup(now)) > cleanupFrequency;
65 | }
66 |
67 | private long getThreshold(long now) {
68 | return (period == 0) ? now : now - period;
69 | }
70 |
71 | private long toMillis(ChuckInterceptor.Period period) {
72 | switch (period) {
73 | case ONE_HOUR:
74 | return TimeUnit.HOURS.toMillis(1);
75 | case ONE_DAY:
76 | return TimeUnit.DAYS.toMillis(1);
77 | case ONE_WEEK:
78 | return TimeUnit.DAYS.toMillis(7);
79 | default:
80 | return 0;
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/SQLiteUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.support;
17 |
18 | import android.content.Context;
19 | import android.content.Intent;
20 | import android.net.Uri;
21 | import android.os.Environment;
22 | import android.widget.Toast;
23 |
24 | import java.io.File;
25 | import java.io.FileInputStream;
26 | import java.io.FileOutputStream;
27 | import java.nio.channels.FileChannel;
28 |
29 | public class SQLiteUtils {
30 |
31 | public static void browseDatabase(Context context) {
32 | if (isIntentResolvable(context, getSQLiteDebuggerAppIntent("/"))) {
33 | String path = extractDatabase(context);
34 | if (path != null) {
35 | Intent intent = getSQLiteDebuggerAppIntent(path);
36 | context.startActivity(intent);
37 | } else {
38 | Toast.makeText(context, "Unable to extract database", Toast.LENGTH_SHORT).show();
39 | }
40 | } else {
41 | Toast.makeText(context, "Unable to resolve a SQLite Intent", Toast.LENGTH_SHORT).show();
42 | }
43 | }
44 |
45 | private static String extractDatabase(Context context) {
46 | try {
47 | File external = context.getExternalFilesDir(null);
48 | File data = Environment.getDataDirectory();
49 | if (external != null && external.canWrite()) {
50 | String dataDBPath = "data/" + context.getPackageName() + "/databases/chuck.db";
51 | String extractDBPath = "chuckdb.temp";
52 | File dataDB = new File(data, dataDBPath);
53 | File extractDB = new File(external, extractDBPath);
54 | if (dataDB.exists()) {
55 | FileChannel in = new FileInputStream(dataDB).getChannel();
56 | FileChannel out = new FileOutputStream(extractDB).getChannel();
57 | out.transferFrom(in, 0, in.size());
58 | in.close();
59 | out.close();
60 | return extractDB.getAbsolutePath();
61 | }
62 | }
63 | } catch (Exception e) {
64 | e.printStackTrace();
65 | }
66 | return null;
67 | }
68 |
69 | private static Intent getSQLiteDebuggerAppIntent(String path) {
70 | Intent intent = new Intent(Intent.ACTION_EDIT);
71 | intent.setData(Uri.parse("sqlite:" + path));
72 | return intent;
73 | }
74 |
75 | private static boolean isIntentResolvable(Context context, Intent intent) {
76 | return context.getPackageManager().resolveActivity(intent, 0) != null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/support/SimpleOnPageChangedListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.support;
17 |
18 | import android.support.v4.view.ViewPager;
19 |
20 | public abstract class SimpleOnPageChangedListener implements ViewPager.OnPageChangeListener {
21 | @Override
22 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
23 | @Override
24 | public abstract void onPageSelected(int position);
25 | @Override
26 | public void onPageScrollStateChanged(int state) {}
27 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/BaseChuckActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.os.Bundle;
19 | import android.support.annotation.Nullable;
20 | import android.support.v7.app.AppCompatActivity;
21 |
22 | import com.readystatesoftware.chuck.internal.support.NotificationHelper;
23 |
24 | public abstract class BaseChuckActivity extends AppCompatActivity {
25 |
26 | private static boolean inForeground;
27 |
28 | private NotificationHelper notificationHelper;
29 |
30 | public static boolean isInForeground() {
31 | return inForeground;
32 | }
33 |
34 | @Override
35 | protected void onCreate(@Nullable Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | notificationHelper = new NotificationHelper(this);
38 | }
39 |
40 | @Override
41 | protected void onResume() {
42 | super.onResume();
43 | inForeground = true;
44 | notificationHelper.dismiss();
45 | }
46 |
47 | @Override
48 | protected void onPause() {
49 | super.onPause();
50 | inForeground = false;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.content.pm.ApplicationInfo;
19 | import android.os.Bundle;
20 | import android.support.annotation.Nullable;
21 | import android.support.v7.widget.Toolbar;
22 |
23 | import com.readystatesoftware.chuck.R;
24 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
25 |
26 | public class MainActivity extends BaseChuckActivity implements TransactionListFragment.OnListFragmentInteractionListener {
27 |
28 | @Override
29 | protected void onCreate(@Nullable Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.chuck_activity_main);
32 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
33 | setSupportActionBar(toolbar);
34 | toolbar.setSubtitle(getApplicationName());
35 | if (savedInstanceState == null) {
36 | getSupportFragmentManager().beginTransaction()
37 | .add(R.id.container, TransactionListFragment.newInstance())
38 | .commit();
39 | }
40 | }
41 |
42 | @Override
43 | public void onListFragmentInteraction(HttpTransaction transaction) {
44 | TransactionActivity.start(this, transaction.getId());
45 | }
46 |
47 | private String getApplicationName() {
48 | ApplicationInfo applicationInfo = getApplicationInfo();
49 | int stringId = applicationInfo.labelRes;
50 | return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : getString(stringId);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/TransactionActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.content.ContentUris;
19 | import android.content.Context;
20 | import android.content.Intent;
21 | import android.database.Cursor;
22 | import android.support.annotation.Nullable;
23 | import android.support.design.widget.TabLayout;
24 | import android.support.v4.app.Fragment;
25 | import android.support.v4.app.FragmentManager;
26 | import android.support.v4.app.FragmentPagerAdapter;
27 | import android.support.v4.app.LoaderManager;
28 | import android.support.v4.content.CursorLoader;
29 | import android.support.v4.content.Loader;
30 | import android.support.v4.view.ViewPager;
31 | import android.support.v7.app.ActionBar;
32 | import android.os.Bundle;
33 | import android.support.v7.widget.Toolbar;
34 | import android.view.Menu;
35 | import android.view.MenuInflater;
36 | import android.view.MenuItem;
37 | import android.widget.TextView;
38 |
39 | import com.readystatesoftware.chuck.R;
40 | import com.readystatesoftware.chuck.internal.data.ChuckContentProvider;
41 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
42 | import com.readystatesoftware.chuck.internal.data.LocalCupboard;
43 | import com.readystatesoftware.chuck.internal.support.FormatUtils;
44 | import com.readystatesoftware.chuck.internal.support.SimpleOnPageChangedListener;
45 |
46 | import java.util.ArrayList;
47 | import java.util.List;
48 |
49 | import static com.readystatesoftware.chuck.internal.ui.TransactionPayloadFragment.TYPE_REQUEST;
50 | import static com.readystatesoftware.chuck.internal.ui.TransactionPayloadFragment.TYPE_RESPONSE;
51 |
52 | public class TransactionActivity extends BaseChuckActivity implements LoaderManager.LoaderCallbacks {
53 |
54 | private static final String ARG_TRANSACTION_ID = "transaction_id";
55 |
56 | private static int selectedTabPosition = 0;
57 |
58 | public static void start(Context context, long transactionId) {
59 | Intent intent = new Intent(context, TransactionActivity.class);
60 | intent.putExtra(ARG_TRANSACTION_ID, transactionId);
61 | context.startActivity(intent);
62 | }
63 |
64 | TextView title;
65 | Adapter adapter;
66 |
67 | private long transactionId;
68 | private HttpTransaction transaction;
69 |
70 | @Override
71 | protected void onCreate(@Nullable Bundle savedInstanceState) {
72 | super.onCreate(savedInstanceState);
73 | setContentView(R.layout.chuck_activity_transaction);
74 |
75 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
76 | setSupportActionBar(toolbar);
77 | title = (TextView) findViewById(R.id.toolbar_title);
78 |
79 | final ActionBar ab = getSupportActionBar();
80 | ab.setDisplayHomeAsUpEnabled(true);
81 |
82 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
83 | if (viewPager != null) {
84 | setupViewPager(viewPager);
85 | }
86 |
87 | TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
88 | tabLayout.setupWithViewPager(viewPager);
89 |
90 | transactionId = getIntent().getLongExtra(ARG_TRANSACTION_ID, 0);
91 | getSupportLoaderManager().initLoader(0, null, this);
92 | }
93 |
94 | @Override
95 | protected void onResume() {
96 | super.onResume();
97 | getSupportLoaderManager().restartLoader(0, null, this);
98 | }
99 |
100 | @Override
101 | public boolean onCreateOptionsMenu(Menu menu) {
102 | MenuInflater inflater = getMenuInflater();
103 | inflater.inflate(R.menu.chuck_transaction, menu);
104 | return super.onCreateOptionsMenu(menu);
105 | }
106 |
107 | @Override
108 | public boolean onOptionsItemSelected(MenuItem item) {
109 | if (item.getItemId() == R.id.share_text) {
110 | share(FormatUtils.getShareText(this, transaction));
111 | return true;
112 | } else if (item.getItemId() == R.id.share_curl) {
113 | share(FormatUtils.getShareCurlCommand(transaction));
114 | return true;
115 | } else {
116 | return super.onOptionsItemSelected(item);
117 | }
118 | }
119 |
120 | @Override
121 | public Loader onCreateLoader(int id, Bundle args) {
122 | CursorLoader loader = new CursorLoader(this);
123 | loader.setUri(ContentUris.withAppendedId(ChuckContentProvider.TRANSACTION_URI, transactionId));
124 | return loader;
125 | }
126 |
127 | @Override
128 | public void onLoadFinished(Loader loader, Cursor data) {
129 | transaction = LocalCupboard.getInstance().withCursor(data).get(HttpTransaction.class);
130 | populateUI();
131 | }
132 |
133 | @Override
134 | public void onLoaderReset(Loader loader) {
135 | }
136 |
137 | private void populateUI() {
138 | if (transaction != null) {
139 | title.setText(transaction.getMethod() + " " + transaction.getPath());
140 | for (TransactionFragment fragment : adapter.fragments) {
141 | fragment.transactionUpdated(transaction);
142 | }
143 | }
144 | }
145 |
146 | private void setupViewPager(ViewPager viewPager) {
147 | adapter = new Adapter(getSupportFragmentManager());
148 | adapter.addFragment(new TransactionOverviewFragment(), getString(R.string.chuck_overview));
149 | adapter.addFragment(TransactionPayloadFragment.newInstance(TYPE_REQUEST), getString(R.string.chuck_request));
150 | adapter.addFragment(TransactionPayloadFragment.newInstance(TYPE_RESPONSE), getString(R.string.chuck_response));
151 | viewPager.setAdapter(adapter);
152 | viewPager.addOnPageChangeListener(new SimpleOnPageChangedListener() {
153 | @Override
154 | public void onPageSelected(int position) {
155 | selectedTabPosition = position;
156 | }
157 | });
158 | viewPager.setCurrentItem(selectedTabPosition);
159 | }
160 |
161 | private void share(String content) {
162 | Intent sendIntent = new Intent();
163 | sendIntent.setAction(Intent.ACTION_SEND);
164 | sendIntent.putExtra(Intent.EXTRA_TEXT, content);
165 | sendIntent.setType("text/plain");
166 | startActivity(Intent.createChooser(sendIntent, null));
167 | }
168 |
169 | static class Adapter extends FragmentPagerAdapter {
170 | final List fragments = new ArrayList<>();
171 | private final List fragmentTitles = new ArrayList<>();
172 |
173 | Adapter(FragmentManager fm) {
174 | super(fm);
175 | }
176 |
177 | void addFragment(TransactionFragment fragment, String title) {
178 | fragments.add(fragment);
179 | fragmentTitles.add(title);
180 | }
181 |
182 | @Override
183 | public Fragment getItem(int position) {
184 | return (Fragment) fragments.get(position);
185 | }
186 |
187 | @Override
188 | public int getCount() {
189 | return fragments.size();
190 | }
191 |
192 | @Override
193 | public CharSequence getPageTitle(int position) {
194 | return fragmentTitles.get(position);
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/TransactionAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.content.Context;
19 | import android.database.Cursor;
20 | import android.support.v4.content.ContextCompat;
21 | import android.support.v4.widget.CursorAdapter;
22 | import android.support.v7.widget.RecyclerView;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.widget.ImageView;
27 | import android.widget.TextView;
28 |
29 | import com.readystatesoftware.chuck.R;
30 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
31 | import com.readystatesoftware.chuck.internal.data.LocalCupboard;
32 | import com.readystatesoftware.chuck.internal.ui.TransactionListFragment.OnListFragmentInteractionListener;
33 |
34 | class TransactionAdapter extends RecyclerView.Adapter {
35 |
36 | private final Context context;
37 | private final OnListFragmentInteractionListener listener;
38 | private final CursorAdapter cursorAdapter;
39 |
40 | private final int colorDefault;
41 | private final int colorRequested;
42 | private final int colorError;
43 | private final int color500;
44 | private final int color400;
45 | private final int color300;
46 |
47 | TransactionAdapter(Context context, OnListFragmentInteractionListener listener) {
48 | this.listener = listener;
49 | this.context = context;
50 | colorDefault = ContextCompat.getColor(context, R.color.chuck_status_default);
51 | colorRequested = ContextCompat.getColor(context, R.color.chuck_status_requested);
52 | colorError = ContextCompat.getColor(context, R.color.chuck_status_error);
53 | color500 = ContextCompat.getColor(context, R.color.chuck_status_500);
54 | color400 = ContextCompat.getColor(context, R.color.chuck_status_400);
55 | color300 = ContextCompat.getColor(context, R.color.chuck_status_300);
56 |
57 | cursorAdapter = new CursorAdapter(TransactionAdapter.this.context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER) {
58 | @Override
59 | public View newView(Context context, Cursor cursor, ViewGroup parent) {
60 | View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.chuck_list_item_transaction, parent, false);
61 | ViewHolder holder = new ViewHolder(itemView);
62 | itemView.setTag(holder);
63 | return itemView;
64 | }
65 |
66 | @Override
67 | public void bindView(View view, final Context context, Cursor cursor) {
68 | final HttpTransaction transaction = LocalCupboard.getInstance().withCursor(cursor).get(HttpTransaction.class);
69 | final ViewHolder holder = (ViewHolder) view.getTag();
70 | holder.path.setText(transaction.getMethod() + " " + transaction.getPath());
71 | holder.host.setText(transaction.getHost());
72 | holder.start.setText(transaction.getRequestStartTimeString());
73 | holder.ssl.setVisibility(transaction.isSsl() ? View.VISIBLE : View.GONE);
74 | if (transaction.getStatus() == HttpTransaction.Status.Complete) {
75 | holder.code.setText(String.valueOf(transaction.getResponseCode()));
76 | holder.duration.setText(transaction.getDurationString());
77 | holder.size.setText(transaction.getTotalSizeString());
78 | } else {
79 | holder.code.setText(null);
80 | holder.duration.setText(null);
81 | holder.size.setText(null);
82 | }
83 | if (transaction.getStatus() == HttpTransaction.Status.Failed) {
84 | holder.code.setText("!!!");
85 | }
86 | setStatusColor(holder, transaction);
87 | holder.transaction = transaction;
88 | holder.view.setOnClickListener(new View.OnClickListener() {
89 | @Override
90 | public void onClick(View v) {
91 | if (null != TransactionAdapter.this.listener) {
92 | TransactionAdapter.this.listener.onListFragmentInteraction(holder.transaction);
93 | }
94 | }
95 | });
96 | }
97 |
98 | private void setStatusColor(ViewHolder holder, HttpTransaction transaction) {
99 | int color;
100 | if (transaction.getStatus() == HttpTransaction.Status.Failed) {
101 | color = colorError;
102 | } else if (transaction.getStatus() == HttpTransaction.Status.Requested) {
103 | color = colorRequested;
104 | } else if (transaction.getResponseCode() >= 500) {
105 | color = color500;
106 | } else if (transaction.getResponseCode() >= 400) {
107 | color = color400;
108 | } else if (transaction.getResponseCode() >= 300) {
109 | color = color300;
110 | } else {
111 | color = colorDefault;
112 | }
113 | holder.code.setTextColor(color);
114 | holder.path.setTextColor(color);
115 | }
116 | };
117 | }
118 |
119 | @Override
120 | public int getItemCount() {
121 | return cursorAdapter.getCount();
122 | }
123 |
124 | @Override
125 | public void onBindViewHolder(ViewHolder holder, int position) {
126 | cursorAdapter.getCursor().moveToPosition(position);
127 | cursorAdapter.bindView(holder.itemView, context, cursorAdapter.getCursor());
128 | }
129 |
130 | @Override
131 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
132 | View v = cursorAdapter.newView(context, cursorAdapter.getCursor(), parent);
133 | return new ViewHolder(v);
134 | }
135 |
136 | void swapCursor(Cursor newCursor) {
137 | cursorAdapter.swapCursor(newCursor);
138 | notifyDataSetChanged();
139 | }
140 |
141 | class ViewHolder extends RecyclerView.ViewHolder {
142 | public final View view;
143 | public final TextView code;
144 | public final TextView path;
145 | public final TextView host;
146 | public final TextView start;
147 | public final TextView duration;
148 | public final TextView size;
149 | public final ImageView ssl;
150 | HttpTransaction transaction;
151 |
152 | ViewHolder(View view) {
153 | super(view);
154 | this.view = view;
155 | code = (TextView) view.findViewById(R.id.code);
156 | path = (TextView) view.findViewById(R.id.path);
157 | host = (TextView) view.findViewById(R.id.host);
158 | start = (TextView) view.findViewById(R.id.start);
159 | duration = (TextView) view.findViewById(R.id.duration);
160 | size = (TextView) view.findViewById(R.id.size);
161 | ssl = (ImageView) view.findViewById(R.id.ssl);
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/TransactionFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
19 |
20 | interface TransactionFragment {
21 | void transactionUpdated(HttpTransaction transaction);
22 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/TransactionListFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.content.Context;
19 | import android.database.Cursor;
20 | import android.os.Bundle;
21 | import android.support.annotation.Nullable;
22 | import android.support.v4.app.Fragment;
23 | import android.support.v4.app.LoaderManager;
24 | import android.support.v4.content.CursorLoader;
25 | import android.support.v4.content.Loader;
26 | import android.support.v7.widget.DividerItemDecoration;
27 | import android.support.v7.widget.LinearLayoutManager;
28 | import android.support.v7.widget.RecyclerView;
29 | import android.support.v7.widget.SearchView;
30 | import android.text.TextUtils;
31 | import android.view.LayoutInflater;
32 | import android.view.Menu;
33 | import android.view.MenuInflater;
34 | import android.view.MenuItem;
35 | import android.view.View;
36 | import android.view.ViewGroup;
37 |
38 | import com.readystatesoftware.chuck.R;
39 | import com.readystatesoftware.chuck.internal.data.ChuckContentProvider;
40 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
41 | import com.readystatesoftware.chuck.internal.support.NotificationHelper;
42 | import com.readystatesoftware.chuck.internal.support.SQLiteUtils;
43 |
44 | public class TransactionListFragment extends Fragment implements
45 | SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks {
46 |
47 | private String currentFilter;
48 | private OnListFragmentInteractionListener listener;
49 | private TransactionAdapter adapter;
50 |
51 | public TransactionListFragment() {}
52 |
53 | public static TransactionListFragment newInstance() {
54 | return new TransactionListFragment();
55 | }
56 |
57 | @Override
58 | public void onCreate(@Nullable Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | setHasOptionsMenu(true);
61 | }
62 |
63 | @Override
64 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
65 | Bundle savedInstanceState) {
66 | View view = inflater.inflate(R.layout.chuck_fragment_transaction_list, container, false);
67 | if (view instanceof RecyclerView) {
68 | Context context = view.getContext();
69 | RecyclerView recyclerView = (RecyclerView) view;
70 | recyclerView.setLayoutManager(new LinearLayoutManager(context));
71 | recyclerView.addItemDecoration(new DividerItemDecoration(getContext(),
72 | DividerItemDecoration.VERTICAL));
73 | adapter = new TransactionAdapter(getContext(), listener);
74 | recyclerView.setAdapter(adapter);
75 | }
76 | return view;
77 | }
78 |
79 | @Override
80 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
81 | super.onActivityCreated(savedInstanceState);
82 | getLoaderManager().initLoader(0, null, this);
83 | }
84 |
85 | @Override
86 | public void onAttach(Context context) {
87 | super.onAttach(context);
88 | if (context instanceof OnListFragmentInteractionListener) {
89 | listener = (OnListFragmentInteractionListener) context;
90 | } else {
91 | throw new RuntimeException(context.toString()
92 | + " must implement OnListFragmentInteractionListener");
93 | }
94 | }
95 |
96 | @Override
97 | public void onDetach() {
98 | super.onDetach();
99 | listener = null;
100 | }
101 |
102 | @Override
103 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
104 | inflater.inflate(R.menu.chuck_main, menu);
105 | MenuItem searchMenuItem = menu.findItem(R.id.search);
106 | SearchView searchView = (SearchView) searchMenuItem.getActionView();
107 | searchView.setOnQueryTextListener(this);
108 | searchView.setIconifiedByDefault(true);
109 | super.onCreateOptionsMenu(menu, inflater);
110 | }
111 |
112 | @Override
113 | public boolean onOptionsItemSelected(MenuItem item) {
114 | if (item.getItemId() == R.id.clear) {
115 | getContext().getContentResolver().delete(ChuckContentProvider.TRANSACTION_URI, null, null);
116 | NotificationHelper.clearBuffer();
117 | return true;
118 | } else if (item.getItemId() == R.id.browse_sql) {
119 | SQLiteUtils.browseDatabase(getContext());
120 | return true;
121 | } else {
122 | return super.onOptionsItemSelected(item);
123 | }
124 | }
125 |
126 | @Override
127 | public Loader onCreateLoader(int id, Bundle args) {
128 | CursorLoader loader = new CursorLoader(getContext());
129 | loader.setUri(ChuckContentProvider.TRANSACTION_URI);
130 | if (!TextUtils.isEmpty(currentFilter)) {
131 | if (TextUtils.isDigitsOnly(currentFilter)) {
132 | loader.setSelection("responseCode LIKE ?");
133 | loader.setSelectionArgs(new String[]{ currentFilter + "%" });
134 | } else {
135 | loader.setSelection("path LIKE ?");
136 | loader.setSelectionArgs(new String[]{ "%" + currentFilter + "%" });
137 | }
138 | }
139 | loader.setProjection(HttpTransaction.PARTIAL_PROJECTION);
140 | loader.setSortOrder("requestDate DESC");
141 | return loader;
142 | }
143 |
144 | @Override
145 | public void onLoadFinished(Loader loader, Cursor data) {
146 | adapter.swapCursor(data);
147 | }
148 |
149 | @Override
150 | public void onLoaderReset(Loader loader) {
151 | adapter.swapCursor(null);
152 | }
153 |
154 | @Override
155 | public boolean onQueryTextSubmit(String query) {
156 | return true;
157 | }
158 |
159 | @Override
160 | public boolean onQueryTextChange(String newText) {
161 | currentFilter = newText;
162 | getLoaderManager().restartLoader(0, null, this);
163 | return true;
164 | }
165 |
166 | public interface OnListFragmentInteractionListener {
167 | void onListFragmentInteraction(HttpTransaction item);
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/TransactionOverviewFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.os.Bundle;
19 | import android.support.annotation.Nullable;
20 | import android.support.v4.app.Fragment;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.widget.TextView;
25 |
26 | import com.readystatesoftware.chuck.R;
27 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
28 |
29 | public class TransactionOverviewFragment extends Fragment implements TransactionFragment {
30 |
31 | TextView url;
32 | TextView method;
33 | TextView protocol;
34 | TextView status;
35 | TextView response;
36 | TextView ssl;
37 | TextView requestTime;
38 | TextView responseTime;
39 | TextView duration;
40 | TextView requestSize;
41 | TextView responseSize;
42 | TextView totalSize;
43 |
44 | private HttpTransaction transaction;
45 |
46 | public TransactionOverviewFragment() {
47 | }
48 |
49 | @Override
50 | public void onCreate(@Nullable Bundle savedInstanceState) {
51 | super.onCreate(savedInstanceState);
52 | setRetainInstance(true);
53 | }
54 |
55 | @Override
56 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
57 | View view = inflater.inflate(R.layout.chuck_fragment_transaction_overview, container, false);
58 | url = (TextView) view.findViewById(R.id.url);
59 | method = (TextView) view.findViewById(R.id.method);
60 | protocol = (TextView) view.findViewById(R.id.protocol);
61 | status = (TextView) view.findViewById(R.id.status);
62 | response = (TextView) view.findViewById(R.id.response);
63 | ssl = (TextView) view.findViewById(R.id.ssl);
64 | requestTime = (TextView) view.findViewById(R.id.request_time);
65 | responseTime = (TextView) view.findViewById(R.id.response_time);
66 | duration = (TextView) view.findViewById(R.id.duration);
67 | requestSize = (TextView) view.findViewById(R.id.request_size);
68 | responseSize = (TextView) view.findViewById(R.id.response_size);
69 | totalSize = (TextView) view.findViewById(R.id.total_size);
70 | return view;
71 | }
72 |
73 | @Override
74 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
75 | super.onViewCreated(view, savedInstanceState);
76 | populateUI();
77 | }
78 |
79 | @Override
80 | public void transactionUpdated(HttpTransaction transaction) {
81 | this.transaction = transaction;
82 | populateUI();
83 | }
84 |
85 | private void populateUI() {
86 | if (isAdded() && transaction != null) {
87 | url.setText(transaction.getUrl());
88 | method.setText(transaction.getMethod());
89 | protocol.setText(transaction.getProtocol());
90 | status.setText(transaction.getStatus().toString());
91 | response.setText(transaction.getResponseSummaryText());
92 | ssl.setText((transaction.isSsl() ? R.string.chuck_yes : R.string.chuck_no));
93 | requestTime.setText(transaction.getRequestDateString());
94 | responseTime.setText(transaction.getResponseDateString());
95 | duration.setText(transaction.getDurationString());
96 | requestSize.setText(transaction.getRequestSizeString());
97 | responseSize.setText(transaction.getResponseSizeString());
98 | totalSize.setText(transaction.getTotalSizeString());
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/library/src/main/java/com/readystatesoftware/chuck/internal/ui/TransactionPayloadFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.internal.ui;
17 |
18 | import android.os.Bundle;
19 | import android.support.annotation.Nullable;
20 | import android.support.v4.app.Fragment;
21 | import android.text.Html;
22 | import android.text.TextUtils;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.widget.TextView;
27 |
28 | import com.readystatesoftware.chuck.R;
29 | import com.readystatesoftware.chuck.internal.data.HttpTransaction;
30 |
31 | public class TransactionPayloadFragment extends Fragment implements TransactionFragment {
32 |
33 | public static final int TYPE_REQUEST = 0;
34 | public static final int TYPE_RESPONSE = 1;
35 |
36 | private static final String ARG_TYPE = "type";
37 |
38 | TextView headers;
39 | TextView body;
40 |
41 | private int type;
42 | private HttpTransaction transaction;
43 |
44 | public TransactionPayloadFragment() {
45 | }
46 |
47 | public static TransactionPayloadFragment newInstance(int type) {
48 | TransactionPayloadFragment fragment = new TransactionPayloadFragment();
49 | Bundle b = new Bundle();
50 | b.putInt(ARG_TYPE, type);
51 | fragment.setArguments(b);
52 | return fragment;
53 | }
54 |
55 | @Override
56 | public void onCreate(@Nullable Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | type = getArguments().getInt(ARG_TYPE);
59 | setRetainInstance(true);
60 | }
61 |
62 | @Override
63 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
64 | @Nullable Bundle savedInstanceState) {
65 | View view = inflater.inflate(R.layout.chuck_fragment_transaction_payload, container, false);
66 | headers = (TextView) view.findViewById(R.id.headers);
67 | body = (TextView) view.findViewById(R.id.body);
68 | return view;
69 | }
70 |
71 | @Override
72 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
73 | super.onViewCreated(view, savedInstanceState);
74 | populateUI();
75 | }
76 |
77 | @Override
78 | public void transactionUpdated(HttpTransaction transaction) {
79 | this.transaction = transaction;
80 | populateUI();
81 | }
82 |
83 | private void populateUI() {
84 | if (isAdded() && transaction != null) {
85 | switch (type) {
86 | case TYPE_REQUEST:
87 | setText(transaction.getRequestHeadersString(true),
88 | transaction.getFormattedRequestBody(), transaction.requestBodyIsPlainText());
89 | break;
90 | case TYPE_RESPONSE:
91 | setText(transaction.getResponseHeadersString(true),
92 | transaction.getFormattedResponseBody(), transaction.responseBodyIsPlainText());
93 | break;
94 | }
95 | }
96 | }
97 |
98 | private void setText(String headersString, String bodyString, boolean isPlainText) {
99 | headers.setVisibility((TextUtils.isEmpty(headersString) ? View.GONE : View.VISIBLE));
100 | headers.setText(Html.fromHtml(headersString));
101 | if (!isPlainText) {
102 | body.setText(getString(R.string.chuck_body_omitted));
103 | } else {
104 | body.setText(bodyString);
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/library/src/main/res/drawable/chuck_ic_delete_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/chuck_ic_https_grey_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/chuck_ic_notification_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/chuck_ic_search_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/chuck_ic_share_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/chuck_activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
24 |
29 |
30 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/chuck_activity_transaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
24 |
25 |
30 |
31 |
38 |
39 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
55 |
60 |
61 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/chuck_fragment_transaction_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
27 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/chuck_fragment_transaction_overview.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
24 |
29 |
30 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
52 |
53 |
58 |
59 |
65 |
66 |
67 |
68 |
71 |
72 |
77 |
78 |
84 |
85 |
86 |
87 |
90 |
91 |
96 |
97 |
103 |
104 |
105 |
106 |
109 |
110 |
115 |
116 |
122 |
123 |
124 |
125 |
128 |
129 |
134 |
135 |
141 |
142 |
143 |
144 |
148 |
149 |
154 |
155 |
161 |
162 |
163 |
164 |
167 |
168 |
173 |
174 |
180 |
181 |
182 |
183 |
186 |
187 |
192 |
193 |
199 |
200 |
201 |
202 |
206 |
207 |
212 |
213 |
219 |
220 |
221 |
222 |
225 |
226 |
231 |
232 |
238 |
239 |
240 |
241 |
244 |
245 |
250 |
251 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/chuck_fragment_transaction_payload.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
24 |
29 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/chuck_list_item_transaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
24 |
33 |
34 |
43 |
44 |
53 |
54 |
64 |
65 |
72 |
73 |
79 |
80 |
87 |
88 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/library/src/main/res/menu/chuck_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/library/src/main/res/menu/chuck_transaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/library/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
27 |
--------------------------------------------------------------------------------
/library/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | #00BCD4
19 | #0097A7
20 | #FF9800
21 |
22 | #212121
23 | #9E9E9E
24 | #F44336
25 | #B71C1C
26 | #FF9800
27 | #0D47A1
28 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Chuck
19 | Recording HTTP activity
20 | Clear
21 | Browse SQLite database
22 | Overview
23 | Request
24 | Response
25 | URL
26 | Method
27 | Protocol
28 | Status
29 | SSL
30 | Request time
31 | Response time
32 | Duration
33 | Request size
34 | Response size
35 | Total size
36 | Yes
37 | No
38 | Share
39 | Share as text
40 | Share as curl command
41 | (encoded or binary body omitted)
42 | Search
43 | \n\n--- Unexpected end of content ---
44 | \n\n--- Content truncated ---
45 | Chuck HTTP notifications
46 |
47 |
--------------------------------------------------------------------------------
/library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
27 |
28 |
31 |
32 |
37 |
38 |
43 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | targetSdkVersion rootProject.ext.targetSdkVersion
10 | applicationId "com.readystatesoftware.chuck.sample"
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 |
15 | buildTypes {
16 | debug {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | debugCompile project(':library')
29 | releaseCompile project(':library-no-op')
30 | compile "com.android.support:design:26.0.0"
31 | compile "com.android.support:appcompat-v7:26.0.0"
32 | compile "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"
33 | compile "com.squareup.retrofit2:retrofit:2.2.0"
34 | compile "com.squareup.retrofit2:converter-gson:2.2.0"
35 | }
36 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jgilfelt/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -dontwarn okio.**
19 | -dontwarn retrofit2.**
20 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/readystatesoftware/chuck/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.sample;
17 |
18 | import android.content.Context;
19 | import android.os.Bundle;
20 | import android.support.annotation.Nullable;
21 | import android.support.v7.app.AppCompatActivity;
22 | import android.view.View;
23 |
24 | import com.readystatesoftware.chuck.Chuck;
25 | import com.readystatesoftware.chuck.ChuckInterceptor;
26 |
27 | import okhttp3.OkHttpClient;
28 | import okhttp3.logging.HttpLoggingInterceptor;
29 | import retrofit2.Call;
30 | import retrofit2.Callback;
31 | import retrofit2.Response;
32 |
33 | public class MainActivity extends AppCompatActivity {
34 |
35 | @Override
36 | protected void onCreate(@Nullable Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_main);
39 | findViewById(R.id.do_http).setOnClickListener(new View.OnClickListener() {
40 | @Override
41 | public void onClick(View view) {
42 | doHttpActivity();
43 | }
44 | });
45 | findViewById(R.id.launch_chuck_directly).setOnClickListener(new View.OnClickListener() {
46 | @Override
47 | public void onClick(View view) {
48 | launchChuckDirectly();
49 | }
50 | });
51 | }
52 |
53 | private OkHttpClient getClient(Context context) {
54 | return new OkHttpClient.Builder()
55 | // Add a ChuckInterceptor instance to your OkHttp client
56 | .addInterceptor(new ChuckInterceptor(context))
57 | .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
58 | .build();
59 | }
60 |
61 | private void launchChuckDirectly() {
62 | // Optionally launch Chuck directly from your own app UI
63 | startActivity(Chuck.getLaunchIntent(this));
64 | }
65 |
66 | private void doHttpActivity() {
67 | SampleApiService.HttpbinApi api = SampleApiService.getInstance(getClient(this));
68 | Callback cb = new Callback() {
69 | @Override public void onResponse(Call call, Response response) {}
70 | @Override public void onFailure(Call call, Throwable t) { t.printStackTrace(); }
71 | };
72 | api.get().enqueue(cb);
73 | api.post(new SampleApiService.Data("posted")).enqueue(cb);
74 | api.patch(new SampleApiService.Data("patched")).enqueue(cb);
75 | api.put(new SampleApiService.Data("put")).enqueue(cb);
76 | api.delete().enqueue(cb);
77 | api.status(201).enqueue(cb);
78 | api.status(401).enqueue(cb);
79 | api.status(500).enqueue(cb);
80 | api.delay(9).enqueue(cb);
81 | api.delay(15).enqueue(cb);
82 | api.redirectTo("https://http2.akamai.com").enqueue(cb);
83 | api.redirect(3).enqueue(cb);
84 | api.redirectRelative(2).enqueue(cb);
85 | api.redirectAbsolute(4).enqueue(cb);
86 | api.stream(500).enqueue(cb);
87 | api.streamBytes(2048).enqueue(cb);
88 | api.image("image/png").enqueue(cb);
89 | api.gzip().enqueue(cb);
90 | api.xml().enqueue(cb);
91 | api.utf8().enqueue(cb);
92 | api.deflate().enqueue(cb);
93 | api.cookieSet("v").enqueue(cb);
94 | api.basicAuth("me", "pass").enqueue(cb);
95 | api.drip(512, 5, 1, 200).enqueue(cb);
96 | api.deny().enqueue(cb);
97 | api.cache("Mon").enqueue(cb);
98 | api.cache(30).enqueue(cb);
99 | }
100 | }
--------------------------------------------------------------------------------
/sample/src/main/java/com/readystatesoftware/chuck/sample/SampleApiService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Jeff Gilfelt.
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 | package com.readystatesoftware.chuck.sample;
17 |
18 | import okhttp3.OkHttpClient;
19 | import retrofit2.Call;
20 | import retrofit2.Retrofit;
21 | import retrofit2.converter.gson.GsonConverterFactory;
22 | import retrofit2.http.Body;
23 | import retrofit2.http.DELETE;
24 | import retrofit2.http.GET;
25 | import retrofit2.http.Header;
26 | import retrofit2.http.PATCH;
27 | import retrofit2.http.POST;
28 | import retrofit2.http.PUT;
29 | import retrofit2.http.Path;
30 | import retrofit2.http.Query;
31 |
32 | class SampleApiService {
33 |
34 | static HttpbinApi getInstance(OkHttpClient client) {
35 | Retrofit retrofit = new Retrofit.Builder()
36 | .baseUrl("https://httpbin.org")
37 | .addConverterFactory(GsonConverterFactory.create())
38 | .client(client)
39 | .build();
40 | return retrofit.create(HttpbinApi.class);
41 | }
42 |
43 | static class Data {
44 | final String thing;
45 | Data(String thing) {
46 | this.thing = thing;
47 | }
48 | }
49 |
50 | interface HttpbinApi {
51 | @GET("/get")
52 | Call get();
53 | @POST("/post")
54 | Call post(@Body Data body);
55 | @PATCH("/patch")
56 | Call patch(@Body Data body);
57 | @PUT("/put")
58 | Call put(@Body Data body);
59 | @DELETE("/delete")
60 | Call delete();
61 | @GET("/status/{code}")
62 | Call status(@Path("code") int code);
63 | @GET("/stream/{lines}")
64 | Call stream(@Path("lines") int lines);
65 | @GET("/stream-bytes/{bytes}")
66 | Call streamBytes(@Path("bytes") int bytes);
67 | @GET("/delay/{seconds}")
68 | Call delay(@Path("seconds") int seconds);
69 | @GET("/redirect-to")
70 | Call redirectTo(@Query("url") String url);
71 | @GET("/redirect/{times}")
72 | Call redirect(@Path("times") int times);
73 | @GET("/relative-redirect/{times}")
74 | Call redirectRelative(@Path("times") int times);
75 | @GET("/absolute-redirect/{times}")
76 | Call redirectAbsolute(@Path("times") int times);
77 | @GET("/image")
78 | Call image(@Header("Accept") String accept);
79 | @GET("/gzip")
80 | Call gzip();
81 | @GET("/xml")
82 | Call xml();
83 | @GET("/encoding/utf8")
84 | Call utf8();
85 | @GET("/deflate")
86 | Call deflate();
87 | @GET("/cookies/set")
88 | Call cookieSet(@Query("k1") String value);
89 | @GET("/basic-auth/{user}/{passwd}")
90 | Call basicAuth(@Path("user") String user, @Path("passwd") String passwd);
91 | @GET("/drip")
92 | Call drip(@Query("numbytes") int bytes, @Query("duration") int seconds, @Query("delay") int delay, @Query("code") int code);
93 | @GET("/deny")
94 | Call deny();
95 | @GET("/cache")
96 | Call cache(@Header("If-Modified-Since") String ifModifiedSince);
97 | @GET("/cache/{seconds}")
98 | Call cache(@Path("seconds") int seconds);
99 | }
100 | }
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
27 |
28 |
35 |
36 |
44 |
45 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spuermax/chuck/152f9a79f94ea23e9f0542137765961918909a76/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #607D8B
4 | #455A64
5 | #448AFF
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Chuck Sample
3 | Do HTTP activity
4 | Launch Chuck directly
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':library', ':library-no-op'
2 |
--------------------------------------------------------------------------------